Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into improvements/connectors-config

pull/10482/head
Dmitriymush 2 years ago
parent
commit
948b0ca842
  1. 15
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 30
      application/src/main/data/json/system/widget_types/bars.json
  3. 29
      application/src/main/data/json/system/widget_types/bars_deprecated.json
  4. 37
      application/src/main/data/json/system/widget_types/polar_area.json
  5. 25
      application/src/main/data/json/system/widget_types/polar_area_deprecated.json
  6. 33
      application/src/main/data/json/system/widget_types/radar.json
  7. 25
      application/src/main/data/json/system/widget_types/radar_deprecated.json
  8. 17
      application/src/main/data/upgrade/3.6.4/schema_update.sql
  9. 25
      application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
  10. 2
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  11. 6
      application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
  12. 31
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  13. 2
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  14. 5
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  15. 7
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  17. 14
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  18. 11
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  19. 12
      application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
  20. 16
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  21. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  22. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  23. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  24. 118
      application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java
  25. 166
      application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java
  26. 103
      application/src/main/java/org/thingsboard/server/service/housekeeper/InMemoryHouseKeeperServiceService.java
  27. 80
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsDeletionTaskProcessor.java
  28. 47
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java
  29. 43
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java
  30. 57
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java
  31. 40
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java
  32. 32
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java
  33. 45
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/LatestTsDeletionTaskProcessor.java
  34. 60
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java
  35. 63
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TenantEntitiesDeletionTaskProcessor.java
  36. 48
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TsHistoryDeletionTaskProcessor.java
  37. 125
      application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java
  38. 37
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  39. 2
      application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
  40. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
  41. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  42. 7
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  43. 60
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  44. 3
      application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java
  45. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  47. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  48. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java
  49. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java
  50. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java
  51. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  52. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
  53. 53
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java
  54. 109
      application/src/main/java/org/thingsboard/server/service/queue/consumer/QueueConsumerManager.java
  55. 33
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  56. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java
  57. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
  58. 21
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  59. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java
  60. 1
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  61. 7
      application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java
  62. 2
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  63. 47
      application/src/main/resources/thingsboard.yml
  64. 41
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  65. 115
      application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java
  66. 24
      application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
  67. 5
      application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java
  68. 87
      application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java
  69. 6
      application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java
  70. 5
      application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java
  71. 46
      application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java
  72. 20
      application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
  73. 5
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  74. 56
      application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
  75. 5
      application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
  76. 5
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  77. 75
      application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java
  78. 169
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  79. 5
      application/src/test/java/org/thingsboard/server/controller/EntityRelationControllerTest.java
  80. 20
      application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java
  81. 4
      application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
  82. 7
      application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java
  83. 5
      application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java
  84. 11
      application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java
  85. 5
      application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java
  86. 49
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  87. 39
      application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java
  88. 19
      application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
  89. 24
      application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java
  90. 5
      application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java
  91. 4
      application/src/test/java/org/thingsboard/server/edge/TenantEdgeTest.java
  92. 7
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  93. 2
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  94. 478
      application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java
  95. 23
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  96. 7
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
  97. 6
      application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java
  98. 16
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  99. 6
      application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
  100. 94
      application/src/test/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManagerTest.java

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

@ -15,18 +15,21 @@
"state_chart",
"bar_chart_with_labels",
"range_chart",
"cards.aggregated_value_card",
"bars",
"pie",
"doughnut",
"horizontal_doughnut",
"polar_area",
"radar",
"charts.basic_timeseries",
"charts.state_chart",
"charts.timeseries_bars_flot",
"cards.aggregated_value_card",
"charts.bars",
"pie",
"charts.pie",
"charts.pie_chart_js",
"doughnut",
"horizontal_doughnut",
"charts.doughnut_chart_js",
"charts.polar_area_chart_js",
"charts.radar_chart_js",
"charts.doughnut_chart_js"
"charts.radar_chart_js"
]
}

30
application/src/main/data/json/system/widget_types/bars.json

File diff suppressed because one or more lines are too long

29
application/src/main/data/json/system/widget_types/bars_deprecated.json

@ -0,0 +1,29 @@
{
"fqn": "charts.bars",
"name": "Bars",
"deprecated": true,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC",
"description": "Displays latest values of the attributes or time-series data for multiple entities as separate bars.",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 5,
"resources": [
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
}
],
"templateHtml": "<canvas id=\"barChart\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-chart-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars\"}"
},
"externalId": null,
"tags": [
"bar",
"bar chart"
]
}

37
application/src/main/data/json/system/widget_types/polar_area.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_types/polar_area_deprecated.json

File diff suppressed because one or more lines are too long

33
application/src/main/data/json/system/widget_types/radar.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_types/radar_deprecated.json

File diff suppressed because one or more lines are too long

17
application/src/main/data/upgrade/3.6.4/schema_update.sql

@ -14,6 +14,23 @@
-- limitations under the License.
--
-- UPDATE PUBLIC CUSTOMERS START
DO
$$
BEGIN
IF NOT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = 'customer' AND column_name = 'is_public'
) THEN
ALTER TABLE customer ADD COLUMN is_public boolean DEFAULT false;
UPDATE customer SET is_public = true WHERE title = 'Public';
END IF;
END;
$$;
-- UPDATE PUBLIC CUSTOMERS END
-- create new attribute_kv table schema
DO
$$

25
application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java

@ -38,10 +38,10 @@ import java.util.stream.Collectors;
*/
public class NetworkReceive implements Receive {
public final static String UNKNOWN_SOURCE = "";
public final static int UNLIMITED = -1;
public final static int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
public final static int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
public static final String UNKNOWN_SOURCE = "";
public static final int UNLIMITED = -1;
public static final int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
public static final int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
private static final Logger log = LoggerFactory.getLogger(NetworkReceive.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
@ -54,27 +54,16 @@ public class NetworkReceive implements Receive {
public NetworkReceive(String source, ByteBuffer buffer) {
this.source = source;
this(TB_MAX_REQUESTED_BUFFER_SIZE, source);
this.buffer = buffer;
this.size = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
this(TB_MAX_REQUESTED_BUFFER_SIZE, source);
}
public NetworkReceive(int maxSize, String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = getMaxSize(maxSize);
this.memoryPool = MemoryPool.NONE;
this(maxSize, source, MemoryPool.NONE);
}
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {

2
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -23,7 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.LinkedHashMapRemoveEldest;
import org.thingsboard.server.actors.ActorSystemContext;

6
application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java

@ -40,9 +40,9 @@ public class TbRuleEngineSecurityConfiguration {
.frameOptions(config -> {}).disable())
.cors(cors -> {})
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests()
.requestMatchers("/actuator/prometheus").permitAll()
.anyRequest().authenticated();
.authorizeHttpRequests(config -> config
.requestMatchers("/actuator/prometheus").permitAll()
.anyRequest().authenticated());
return http.build();
}
}

31
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -72,12 +72,11 @@ public class ThingsboardSecurityConfiguration {
public static final String JWT_TOKEN_HEADER_PARAM_V2 = "Authorization";
public static final String JWT_TOKEN_QUERY_PARAM = "token";
public static final String WEBJARS_ENTRY_POINT = "/webjars/**";
public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**", "/api/images/public/**"};
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[]{"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**", "/api/images/public/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String WS_ENTRY_POINT = "/api/ws/**";
public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code";
@ -153,7 +152,7 @@ public class ThingsboardSecurityConfiguration {
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List<String> pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT,
PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT,
DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
@ -209,18 +208,18 @@ public class ThingsboardSecurityConfiguration {
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(config -> {})
.sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(config -> config
.requestMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars
.requestMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
.requestMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.requestMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point
.requestMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.requestMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url
.requestMatchers(DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT).permitAll() // Device connectivity certificate (public)
.requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll()) // static resources, user activation and password reset end-points
.authorizeRequests(config -> config
.requestMatchers(WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points
.requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()) // Protected API End-points
.authorizeHttpRequests(config -> config
.requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points (webjars included)
.requestMatchers(
DEVICE_API_ENTRY_POINT, // Device HTTP Transport API
FORM_BASED_LOGIN_ENTRY_POINT, // Login end-point
PUBLIC_LOGIN_ENTRY_POINT, // Public login end-point
TOKEN_REFRESH_ENTRY_POINT, // Token refresh end-point
MAIL_OAUTH2_PROCESSING_ENTRY_POINT, // Mail oauth2 code processing url
DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT, // Device connectivity certificate (public)
WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points
.requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.anyRequest().permitAll())
.exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler))
.addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
@ -243,7 +242,7 @@ public class ThingsboardSecurityConfiguration {
@Bean
@ConditionalOnMissingBean(CorsFilter.class)
public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {
if (mvcCorsProperties.getMappings().size() == 0) {
if (mvcCorsProperties.getMappings().isEmpty()) {
return new CorsFilter(new UrlBasedCorsConfigurationSource());
} else {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

2
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -19,7 +19,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;

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

@ -21,14 +21,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -48,7 +45,6 @@ import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
@ -56,7 +52,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterableByTenant;
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;

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

@ -110,7 +110,7 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.5.1 to 3.6.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
dataUpdateService.updateData("3.5.1");
systemDataLoaderService.updateDefaultNotificationConfigs();
systemDataLoaderService.updateDefaultNotificationConfigs(true);
case "3.6.0":
log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.0");
@ -126,13 +126,16 @@ public class ThingsboardInstallService {
case "3.6.2":
log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.2");
systemDataLoaderService.updateDefaultNotificationConfigs();
systemDataLoaderService.updateDefaultNotificationConfigs(true);
case "3.6.3":
log.info("Upgrading ThingsBoard from version 3.6.3 to 3.6.4 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.3");
case "3.6.4":
log.info("Upgrading ThingsBoard from version 3.6.4 to 3.7.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.4");
dataUpdateService.updateData("3.6.4");
entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists();
systemDataLoaderService.updateDefaultNotificationConfigs(false);
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:

2
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;

14
application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
@ -44,6 +45,7 @@ import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.RelationActionEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserServiceImpl;
import javax.annotation.PostConstruct;
@ -69,6 +71,7 @@ public class EdgeEventSourcingListener {
private final TbClusterService tbClusterService;
private final EdgeSynchronizationManager edgeSynchronizationManager;
private final TenantService tenantService;
@PostConstruct
public void init() {
@ -95,19 +98,24 @@ public class EdgeEventSourcingListener {
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent<?> event) {
TenantId tenantId = event.getTenantId();
EntityType entityType = event.getEntityId().getEntityType();
if (!tenantId.isSysTenantId() && !tenantService.tenantExists(tenantId)) {
log.debug("[{}] Ignoring DeleteEntityEvent because tenant does not exist: {}", tenantId, event);
return;
}
try {
if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) {
return;
}
log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event);
log.trace("[{}] DeleteEntityEvent called: {}", tenantId, event);
EdgeEventType type = getEdgeEventTypeForEntityEvent(event.getEntity());
EdgeEventActionType actionType = getEdgeEventActionTypeForEntityEvent(event.getEntity());
tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(),
tbClusterService.sendNotificationMsgToEdge(tenantId, null, event.getEntityId(),
JacksonUtil.toString(event.getEntity()), type, actionType,
edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) {
log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event, e);
log.error("[{}] failed to process DeleteEntityEvent: {}", tenantId, event, e);
}
}

11
application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java

@ -27,12 +27,8 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -71,13 +67,6 @@ public abstract class AbstractTbEntityService {
@Lazy
private EntitiesVersionControlService vcService;
protected void removeAlarmsByOriginatorId(TenantId tenantId, EntityId entityId) {
PageData<AlarmId> alarms =
alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, new TimePageLink(Integer.MAX_VALUE));
alarms.getData().forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId));
}
protected <T> T checkNotNull(T reference) throws ThingsboardException {
return checkNotNull(reference, "Requested item wasn't found!");
}

12
application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java

@ -51,6 +51,7 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNoti
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.PostConstruct;
import java.util.Set;
@ -61,6 +62,7 @@ import java.util.Set;
public class EntityStateSourcingListener {
private final TbClusterService tbClusterService;
private final TenantService tenantService;
@PostConstruct
public void init() {
@ -71,6 +73,9 @@ public class EntityStateSourcingListener {
public void handleEvent(SaveEntityEvent<?> event) {
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
if (entityId == null) {
return;
}
EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity save event: {}", tenantId, entityType, entityId, event);
boolean isCreated = event.getCreated() != null && event.getCreated();
@ -120,7 +125,14 @@ public class EntityStateSourcingListener {
public void handleEvent(DeleteEntityEvent<?> event) {
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
if (entityId == null) {
return;
}
EntityType entityType = entityId.getEntityType();
if (!tenantId.isSysTenantId() && entityType != EntityType.TENANT && !tenantService.tenantExists(tenantId)) {
log.debug("[{}] Ignoring DeleteEntityEvent because tenant does not exist: {}", tenantId, event);
return;
}
log.debug("[{}][{}][{}] Handling entity deletion event: {}", tenantId, entityType, entityId, event);
switch (entityType) {

16
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -176,16 +176,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}
@Override
public List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs) {
public List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs) {
List<AlarmId> totalAlarmIds = new ArrayList<>();
PageLink pageLink = new PageLink(100, 0, null, new SortOrder("id", SortOrder.Direction.ASC));
while (true) {
PageData<AlarmId> pageData = alarmService.findAlarmIdsByAssigneeId(user.getTenantId(), user.getId(), pageLink);
PageData<AlarmId> pageData = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, pageLink);
List<AlarmId> alarmIds = pageData.getData();
if (alarmIds.isEmpty()) {
break;
}
processAlarmsUnassignment(tenantId, user, alarmIds, unassignTs);
processAlarmsUnassignment(tenantId, userId, userTitle, alarmIds, unassignTs);
totalAlarmIds.addAll(alarmIds);
pageLink = pageLink.nextPageLink();
}
@ -204,16 +204,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
return ts > 0 ? ts : System.currentTimeMillis();
}
private void processAlarmsUnassignment(TenantId tenantId, User user, List<AlarmId> alarmIds, long unassignTs) {
private void processAlarmsUnassignment(TenantId tenantId, UserId userId, String userTitle, List<AlarmId> alarmIds, long unassignTs) {
for (AlarmId alarmId : alarmIds) {
log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, user.getId());
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(user.getTenantId(), alarmId, unassignTs);
log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, userId);
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarmId, unassignTs);
if (!result.isSuccessful()) {
log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, user.getId());
log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, userId);
continue;
}
if (result.isModified()) {
String comment = String.format("Alarm was unassigned because user %s - was deleted", user.getTitle());
String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle);
addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment);
logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null);
}

2
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java

@ -41,7 +41,7 @@ public interface TbAlarmService {
AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException;
List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs);
List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs);
Boolean delete(Alarm alarm, User user);
}

1
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -61,7 +61,6 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
TenantId tenantId = asset.getTenantId();
AssetId assetId = asset.getId();
try {
removeAlarmsByOriginatorId(tenantId, assetId);
assetService.deleteAsset(tenantId, assetId);
logEntityActionService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString());
} catch (Exception e) {

1
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -94,7 +94,6 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId();
try {
removeAlarmsByOriginatorId(tenantId, deviceId);
deviceService.deleteDevice(tenantId, deviceId);
logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), actionType,
user, deviceId.toString());

118
application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java

@ -0,0 +1,118 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos.HousekeeperTaskProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.housekeeper.HousekeeperConfig;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.consumer.QueueConsumerManager;
import javax.annotation.PreDestroy;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@TbCoreComponent
@Service
@Slf4j
public class HousekeeperReprocessingService {
private final HousekeeperConfig config;
private final HousekeeperService housekeeperService;
private final QueueConsumerManager<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer;
private final TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> producer;
private final TopicPartitionInfo submitTpi;
private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-reprocessing-consumer"));
public HousekeeperReprocessingService(HousekeeperConfig config,
@Lazy HousekeeperService housekeeperService,
TbCoreQueueFactory queueFactory) {
this.config = config;
this.housekeeperService = housekeeperService;
this.consumer = QueueConsumerManager.<TbProtoQueueMsg<ToHousekeeperServiceMsg>>builder()
.name("Housekeeper reprocessing")
.msgPackProcessor(this::processMsgs)
.pollInterval(config.getPollInterval())
.consumerCreator(queueFactory::createHousekeeperReprocessingMsgConsumer)
.consumerExecutor(consumerExecutor)
.build();
this.producer = queueFactory.createHousekeeperReprocessingMsgProducer();
this.submitTpi = TopicPartitionInfo.builder().topic(producer.getDefaultTopic()).build();
}
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void afterStartUp() {
consumer.subscribe(); // Kafka topic for tasks reprocessing has only 1 partition, so only one TB Core will reprocess tasks
consumer.launch();
}
private void processMsgs(List<TbProtoQueueMsg<ToHousekeeperServiceMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer) throws Exception {
Thread.sleep(config.getTaskReprocessingDelay());
for (TbProtoQueueMsg<ToHousekeeperServiceMsg> msg : msgs) {
log.trace("Reprocessing task: {}", msg);
try {
housekeeperService.processTask(msg.getValue());
} catch (InterruptedException e) {
return;
} catch (Throwable e) {
log.error("Unexpected error during message reprocessing [{}]", msg, e);
submitForReprocessing(msg.getValue(), e);
}
}
consumer.commit();
}
public void submitForReprocessing(ToHousekeeperServiceMsg msg, Throwable error) {
HousekeeperTaskProto task = msg.getTask();
Set<String> errors = new LinkedHashSet<>(task.getErrorsList());
errors.add(StringUtils.truncate(ExceptionUtils.getStackTrace(error), 1024));
msg = msg.toBuilder()
.setTask(task.toBuilder()
.setAttempt(task.getAttempt() + 1)
.clearErrors().addAllErrors(errors)
.build())
.build();
log.trace("Submitting for reprocessing: {}", msg);
producer.send(submitTpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null);
}
@PreDestroy
private void stop() {
consumer.stop();
consumerExecutor.shutdownNow();
log.info("Stopped Housekeeper reprocessing service");
}
}

166
application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java

@ -0,0 +1,166 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.housekeeper.HousekeeperConfig;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.housekeeper.processor.HousekeeperTaskProcessor;
import org.thingsboard.server.service.housekeeper.stats.HousekeeperStatsService;
import org.thingsboard.server.service.queue.consumer.QueueConsumerManager;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@TbCoreComponent
@Service
@Slf4j
public class HousekeeperService {
private final Map<HousekeeperTaskType, HousekeeperTaskProcessor<?>> taskProcessors;
private final HousekeeperConfig config;
private final HousekeeperReprocessingService reprocessingService;
private final Optional<HousekeeperStatsService> statsService;
private final NotificationRuleProcessor notificationRuleProcessor;
private final QueueConsumerManager<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer;
private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-consumer"));
private final ExecutorService taskExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-task-processor"));
public HousekeeperService(HousekeeperConfig config,
HousekeeperReprocessingService reprocessingService,
TbCoreQueueFactory queueFactory,
Optional<HousekeeperStatsService> statsService,
NotificationRuleProcessor notificationRuleProcessor,
@Lazy List<HousekeeperTaskProcessor<?>> taskProcessors) {
this.config = config;
this.reprocessingService = reprocessingService;
this.statsService = statsService;
this.notificationRuleProcessor = notificationRuleProcessor;
this.consumer = QueueConsumerManager.<TbProtoQueueMsg<ToHousekeeperServiceMsg>>builder()
.name("Housekeeper")
.msgPackProcessor(this::processMsgs)
.pollInterval(config.getPollInterval())
.consumerCreator(queueFactory::createHousekeeperMsgConsumer)
.consumerExecutor(consumerExecutor)
.build();
this.taskProcessors = taskProcessors.stream().collect(Collectors.toMap(HousekeeperTaskProcessor::getTaskType, p -> p));
}
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void afterStartUp() {
consumer.subscribe();
consumer.launch();
}
private void processMsgs(List<TbProtoQueueMsg<ToHousekeeperServiceMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer) {
for (TbProtoQueueMsg<ToHousekeeperServiceMsg> msg : msgs) {
log.trace("Processing task: {}", msg);
try {
processTask(msg.getValue());
} catch (InterruptedException e) {
return;
} catch (Throwable e) {
log.error("Unexpected error during message processing [{}]", msg, e);
reprocessingService.submitForReprocessing(msg.getValue(), e);
}
}
consumer.commit();
}
@SuppressWarnings("unchecked")
protected <T extends HousekeeperTask> void processTask(ToHousekeeperServiceMsg msg) throws Exception {
HousekeeperTask task = JacksonUtil.fromString(msg.getTask().getValue(), HousekeeperTask.class);
HousekeeperTaskType taskType = task.getTaskType();
if (config.getDisabledTaskTypes().contains(taskType)) {
log.debug("Task type {} is disabled, ignoring {}", taskType, task);
return;
}
HousekeeperTaskProcessor<T> taskProcessor = (HousekeeperTaskProcessor<T>) taskProcessors.get(taskType);
if (taskProcessor == null) {
throw new IllegalArgumentException("Unsupported task type " + taskType);
}
try {
long startTs = System.currentTimeMillis();
Future<Object> future = taskExecutor.submit(() -> {
taskProcessor.process((T) task);
return null;
});
future.get(config.getTaskProcessingTimeout(), TimeUnit.MILLISECONDS);
long timing = System.currentTimeMillis() - startTs;
if (log.isDebugEnabled()) {
log.debug("[{}] Processed {} in {} ms (attempt {})", task.getTenantId(), task.getDescription(), timing, msg.getTask().getAttempt());
}
statsService.ifPresent(statsService -> statsService.reportProcessed(taskType, msg, timing));
} catch (InterruptedException e) {
throw e;
} catch (Throwable e) {
Throwable error = e;
if (e instanceof ExecutionException) {
error = e.getCause();
} else if (e instanceof TimeoutException) {
error = new TimeoutException("Timeout after " + config.getTaskProcessingTimeout() + " seconds");
}
if (msg.getTask().getAttempt() < config.getMaxReprocessingAttempts()) {
log.warn("[{}] Failed to process {} (attempt {}), submitting for reprocessing",
task.getTenantId(), task.getDescription(), msg.getTask().getAttempt(), error);
reprocessingService.submitForReprocessing(msg, error);
} else {
log.error("[{}] Failed to process task in {} attempts: {}", task.getTenantId(), msg.getTask().getAttempt(), msg, e);
notificationRuleProcessor.process(TaskProcessingFailureTrigger.builder()
.task(task)
.error(error)
.attempt(msg.getTask().getAttempt())
.build());
}
statsService.ifPresent(statsService -> statsService.reportFailure(taskType, msg));
}
}
@PreDestroy
private void stop() {
consumer.stop();
consumerExecutor.shutdownNow();
log.info("Stopped Housekeeper service");
}
}

103
application/src/main/java/org/thingsboard/server/service/housekeeper/InMemoryHouseKeeperServiceService.java

@ -1,103 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
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.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.housekeeper.HouseKeeperService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@RequiredArgsConstructor
@Slf4j
public class InMemoryHouseKeeperServiceService implements HouseKeeperService {
final TbAlarmService alarmService;
ListeningExecutorService executor;
AtomicInteger queueSize = new AtomicInteger();
AtomicInteger totalProcessedCounter = new AtomicInteger();
@PostConstruct
public void init() {
log.debug("Starting HouseKeeper service");
executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper")));
}
@PreDestroy
public void destroy() {
if (executor != null) {
log.debug("Stopping HouseKeeper service");
executor.shutdown();
}
}
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent<?> event) {
log.trace("[{}] DeleteEntityEvent handler: {}", event.getTenantId(), event);
EntityId entityId = event.getEntityId();
if (EntityType.USER.equals(entityId.getEntityType())) {
unassignDeletedUserAlarms(event.getTenantId(), (User) event.getEntity(), event.getTs());
}
}
@Override
public ListenableFuture<List<AlarmId>> unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs) {
log.debug("[{}][{}] unassignDeletedUserAlarms submitting, pending queue size: {} ", tenantId, user.getId().getId(), queueSize.get());
queueSize.incrementAndGet();
ListenableFuture<List<AlarmId>> future = executor.submit(() -> alarmService.unassignDeletedUserAlarms(tenantId, user, unassignTs));
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(List<AlarmId> alarmIds) {
queueSize.decrementAndGet();
totalProcessedCounter.incrementAndGet();
log.debug("[{}][{}] unassignDeletedUserAlarms finished, pending queue size: {}, total processed count: {} ",
tenantId, user.getId().getId(), queueSize.get(), totalProcessedCounter.get());
}
@Override
public void onFailure(Throwable throwable) {
queueSize.decrementAndGet();
totalProcessedCounter.incrementAndGet();
log.error("[{}][{}] unassignDeletedUserAlarms failed, pending queue size: {}, total processed count: {}",
tenantId, user.getId().getId(), queueSize.get(), totalProcessedCounter.get(), throwable);
}
}, MoreExecutors.directExecutor());
return future;
}
}

80
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsDeletionTaskProcessor.java

@ -0,0 +1,80 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.housekeeper.AlarmsDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
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.util.TbPair;
import org.thingsboard.server.dao.alarm.AlarmService;
import java.util.List;
import java.util.UUID;
@Component
@RequiredArgsConstructor
@Slf4j
public class AlarmsDeletionTaskProcessor extends HousekeeperTaskProcessor<AlarmsDeletionHousekeeperTask> {
private final AlarmService alarmService;
@Override
public void process(AlarmsDeletionHousekeeperTask task) throws Exception {
EntityId entityId = task.getEntityId();
EntityType entityType = entityId.getEntityType();
TenantId tenantId = task.getTenantId();
if (entityType == EntityType.DEVICE || entityType == EntityType.ASSET) {
if (task.getAlarms() == null) {
AlarmId lastId = null;
long lastCreatedTime = 0;
while (true) {
List<TbPair<UUID, Long>> alarms = alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, lastCreatedTime, lastId, 128);
if (alarms.isEmpty()) {
break;
}
housekeeperClient.submitTask(new AlarmsDeletionHousekeeperTask(tenantId, entityId, alarms.stream().map(TbPair::getFirst).toList()));
TbPair<UUID, Long> last = alarms.get(alarms.size() - 1);
lastId = new AlarmId(last.getFirst());
lastCreatedTime = last.getSecond();
log.debug("[{}][{}][{}] Submitted task for deleting {} alarms", tenantId, entityType, entityId, alarms.size());
}
} else {
for (UUID alarmId : task.getAlarms()) {
alarmService.delAlarm(tenantId, new AlarmId(alarmId));
}
log.debug("[{}][{}][{}] Deleted {} alarms", tenantId, entityType, entityId, task.getAlarms().size());
}
}
int count = alarmService.deleteEntityAlarmRecords(tenantId, entityId);
log.debug("[{}][{}][{}] Deleted {} entity alarms", tenantId, entityType, entityId, count);
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_ALARMS;
}
}

47
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.AlarmsUnassignHousekeeperTask;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class AlarmsUnassignTaskProcessor extends HousekeeperTaskProcessor<AlarmsUnassignHousekeeperTask> {
private final TbAlarmService alarmService;
@Override
public void process(AlarmsUnassignHousekeeperTask task) throws Exception {
List<AlarmId> alarms = alarmService.unassignDeletedUserAlarms(task.getTenantId(), (UserId) task.getEntityId(), task.getUserTitle(), task.getTs());
log.debug("[{}][{}] Unassigned {} alarms", task.getTenantId(), task.getEntityId(), alarms.size());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.UNASSIGN_ALARMS;
}
}

43
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
@Component
@RequiredArgsConstructor
@Slf4j
public class AttributesDeletionTaskProcessor extends HousekeeperTaskProcessor<HousekeeperTask> {
private final AttributesService attributesService;
@Override
public void process(HousekeeperTask task) throws Exception {
int deletedCount = attributesService.removeAllByEntityId(task.getTenantId(), task.getEntityId());
log.debug("[{}][{}][{}] Deleted {} attributes", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), deletedCount);
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_ATTRIBUTES;
}
}

57
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.housekeeper.EntitiesDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import java.util.UUID;
@Component
@RequiredArgsConstructor
@Slf4j
public class EntitiesDeletionTaskProcessor extends HousekeeperTaskProcessor<EntitiesDeletionHousekeeperTask> {
private final EntityServiceRegistry entityServiceRegistry;
@Override
public void process(EntitiesDeletionHousekeeperTask task) throws Exception {
EntityType entityType = task.getEntityType();
TenantId tenantId = task.getTenantId();
EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
for (UUID entityUuid : task.getEntities()) {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityUuid);
entityService.deleteEntity(tenantId, entityId, true);
}
log.debug("[{}] Deleted {} {}s", tenantId, task.getEntities().size(), entityType.getNormalName().toLowerCase());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_ENTITIES;
}
}

40
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
@Component
@RequiredArgsConstructor
public class EventsDeletionTaskProcessor extends HousekeeperTaskProcessor<HousekeeperTask> {
private final EventService eventService;
@Override
public void process(HousekeeperTask task) throws Exception {
eventService.removeEvents(task.getTenantId(), task.getEntityId(), null, 0L, System.currentTimeMillis());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_EVENTS;
}
}

32
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.msg.housekeeper.HousekeeperClient;
public abstract class HousekeeperTaskProcessor<T extends HousekeeperTask> {
@Autowired
protected HousekeeperClient housekeeperClient;
public abstract void process(T task) throws Exception;
public abstract HousekeeperTaskType getTaskType();
}

45
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/LatestTsDeletionTaskProcessor.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.LatestTsDeletionHousekeeperTask;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class LatestTsDeletionTaskProcessor extends HousekeeperTaskProcessor<LatestTsDeletionHousekeeperTask> {
private final TimeseriesService timeseriesService;
@Override
public void process(LatestTsDeletionHousekeeperTask task) throws Exception {
timeseriesService.removeLatest(task.getTenantId(), task.getEntityId(), List.of(task.getKey())).get();
log.debug("[{}][{}][{}] Deleted latest telemetry for key '{}'", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), task.getKey());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_LATEST_TS;
}
}

60
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.LatestTsDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.TsHistoryDeletionHousekeeperTask;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class TelemetryDeletionTaskProcessor extends HousekeeperTaskProcessor<HousekeeperTask> {
private final TimeseriesService timeseriesService;
@Override
public void process(HousekeeperTask task) throws Exception {
TenantId tenantId = task.getTenantId();
EntityId entityId = task.getEntityId();
List<String> keys = timeseriesService.findAllKeysByEntityIds(tenantId, List.of(entityId));
for (String key : keys) {
var latestTsDeletionTask = new LatestTsDeletionHousekeeperTask(tenantId, entityId, key);
housekeeperClient.submitTask(latestTsDeletionTask);
var tsHistoryDeletionTask = new TsHistoryDeletionHousekeeperTask(tenantId, entityId, key);
housekeeperClient.submitTask(tsHistoryDeletionTask);
}
log.trace("[{}][{}][{}] Submitted latest and ts history deletion tasks for {} keys", tenantId, entityId.getEntityType(), entityId, keys.size());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_TELEMETRY;
}
}

63
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TenantEntitiesDeletionTaskProcessor.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.housekeeper.EntitiesDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.TenantEntitiesDeletionHousekeeperTask;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.entity.EntityDaoRegistry;
import java.util.List;
import java.util.UUID;
@Component
@RequiredArgsConstructor
@Slf4j
public class TenantEntitiesDeletionTaskProcessor extends HousekeeperTaskProcessor<TenantEntitiesDeletionHousekeeperTask> {
private final EntityDaoRegistry entityDaoRegistry;
@Override
public void process(TenantEntitiesDeletionHousekeeperTask task) throws Exception {
EntityType entityType = task.getEntityType();
TenantId tenantId = task.getTenantId();
Dao<?> entityDao = entityDaoRegistry.getDao(entityType);
UUID last = null;
while (true) {
List<UUID> entities = entityDao.findIdsByTenantIdAndIdOffset(tenantId, last, 128);
if (entities.isEmpty()) {
break;
}
housekeeperClient.submitTask(new EntitiesDeletionHousekeeperTask(tenantId, entityType, entities));
last = entities.get(entities.size() - 1);
log.debug("[{}] Submitted task for deleting {} {}s", tenantId, entities.size(), entityType.getNormalName().toLowerCase());
}
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_TENANT_ENTITIES;
}
}

48
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TsHistoryDeletionTaskProcessor.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.TsHistoryDeletionHousekeeperTask;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class TsHistoryDeletionTaskProcessor extends HousekeeperTaskProcessor<TsHistoryDeletionHousekeeperTask> {
private final TimeseriesService timeseriesService;
@Override
public void process(TsHistoryDeletionHousekeeperTask task) throws Exception {
DeleteTsKvQuery deleteQuery = new BaseDeleteTsKvQuery(task.getKey(), 0, System.currentTimeMillis(), false, false);
timeseriesService.remove(task.getTenantId(), task.getEntityId(), List.of(deleteQuery)).get();
log.debug("[{}][{}][{}] Deleted timeseries history for key '{}'", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), task.getKey());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_TS_HISTORY;
}
}

125
application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java

@ -0,0 +1,125 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.stats;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.stats.DefaultCounter;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsTimer;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@Slf4j
@ConditionalOnProperty(name = "queue.core.housekeeper.stats.enabled", havingValue = "true", matchIfMissing = true)
public class HousekeeperStatsService {
private final Map<HousekeeperTaskType, HousekeeperStats> stats = new EnumMap<>(HousekeeperTaskType.class);
public HousekeeperStatsService(StatsFactory statsFactory) {
for (HousekeeperTaskType taskType : HousekeeperTaskType.values()) {
stats.put(taskType, new HousekeeperStats(taskType, statsFactory));
}
}
@Scheduled(initialDelayString = "${queue.core.housekeeper.stats.print-interval-ms:60000}",
fixedDelayString = "${queue.core.housekeeper.stats.print-interval-ms:60000}")
private void reportStats() {
String statsStr = stats.values().stream().map(stats -> {
String countersStr = stats.getCounters().stream()
.filter(counter -> counter.get() > 0)
.map(counter -> counter.getName() + " = [" + counter.get() + "]")
.collect(Collectors.joining(" "));
if (countersStr.isEmpty()) {
return null;
} else {
return stats.getTaskType() + " " + countersStr + " avgProcessingTime [" + stats.getProcessingTimer().getAvg() + " ms]";
}
}).filter(Objects::nonNull).collect(Collectors.joining(", "));
if (!statsStr.isEmpty()) {
stats.values().forEach(HousekeeperStats::reset);
log.info("Housekeeper stats: {}", statsStr);
}
}
public void reportProcessed(HousekeeperTaskType taskType, ToHousekeeperServiceMsg msg, long timing) {
HousekeeperStats stats = this.stats.get(taskType);
if (msg.getTask().getErrorsCount() == 0) {
stats.getProcessedCounter().increment();
} else {
stats.getReprocessedCounter().increment();
}
stats.getProcessingTimer().record(timing);
}
public void reportFailure(HousekeeperTaskType taskType, ToHousekeeperServiceMsg msg) {
HousekeeperStats stats = this.stats.get(taskType);
if (msg.getTask().getErrorsCount() == 0) {
stats.getFailedProcessingCounter().increment();
} else {
stats.getFailedReprocessingCounter().increment();
}
}
@Getter
static class HousekeeperStats {
private final HousekeeperTaskType taskType;
private final List<StatsCounter> counters = new ArrayList<>();
private final StatsCounter processedCounter;
private final StatsCounter failedProcessingCounter;
private final StatsCounter reprocessedCounter;
private final StatsCounter failedReprocessingCounter;
private final StatsTimer processingTimer;
public HousekeeperStats(HousekeeperTaskType taskType, StatsFactory statsFactory) {
this.taskType = taskType;
this.processedCounter = register("processed", statsFactory);
this.failedProcessingCounter = register("failedProcessing", statsFactory);
this.reprocessedCounter = register("reprocessed", statsFactory);
this.failedReprocessingCounter = register("failedReprocessing", statsFactory);
this.processingTimer = statsFactory.createTimer(StatsType.HOUSEKEEPER, "processingTime", "taskType", taskType.name());
}
private StatsCounter register(String statsName, StatsFactory statsFactory) {
StatsCounter counter = statsFactory.createStatsCounter(StatsType.HOUSEKEEPER.getName(), statsName, "taskType", taskType.name());
counters.add(counter);
return counter;
}
public void reset() {
counters.forEach(DefaultCounter::clear);
processingTimer.reset();
}
}
}

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

@ -549,7 +549,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
} else {
ListenableFuture<List<String>> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())));
, System.currentTimeMillis())));
addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value));
}
}
@ -693,23 +693,26 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Override
@SneakyThrows
public void updateDefaultNotificationConfigs() {
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4));
log.info("Updating default edge failure notification configs for all tenants");
AtomicInteger count = new AtomicInteger();
for (TenantId tenantId : tenants) {
executor.submit(() -> {
notificationSettingsService.updateDefaultNotificationConfigs(tenantId);
int n = count.incrementAndGet();
if (n % 500 == 0) {
log.info("{} tenants processed", n);
}
});
}
executor.shutdown();
executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
public void updateDefaultNotificationConfigs(boolean updateTenants) {
log.info("Updating notification configs...");
notificationSettingsService.updateDefaultNotificationConfigs(TenantId.SYS_TENANT_ID);
if (updateTenants) {
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4));
AtomicInteger count = new AtomicInteger();
for (TenantId tenantId : tenants) {
executor.submit(() -> {
notificationSettingsService.updateDefaultNotificationConfigs(tenantId);
int n = count.incrementAndGet();
if (n % 500 == 0) {
log.info("{} tenants processed", n);
}
});
}
executor.shutdown();
executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
}
}

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

@ -21,4 +21,6 @@ public interface EntityDatabaseSchemaService extends DatabaseSchemaService {
void createOrUpdateViewsAndFunctions() throws Exception;
void createCustomerTitleUniqueConstraintIfNotExists();
}

6
application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java

@ -52,4 +52,10 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer
log.info("Installing SQL DataBase schema views and functions: " + SCHEMA_VIEWS_AND_FUNCTIONS_SQL);
executeQueryFromFile(SCHEMA_VIEWS_AND_FUNCTIONS_SQL);
}
@Override
public void createCustomerTitleUniqueConstraintIfNotExists() {
executeQuery("DO $$ BEGIN IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'customer_title_unq_key') THEN " +
"ALTER TABLE customer ADD CONSTRAINT customer_title_unq_key UNIQUE(tenant_id, title); END IF; END; $$;");
}
}

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

@ -35,6 +35,6 @@ public interface SystemDataLoaderService {
void createDefaultNotificationConfigs();
void updateDefaultNotificationConfigs();
void updateDefaultNotificationConfigs(boolean updateTenants);
}

7
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -27,7 +27,6 @@ import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.Optional;
import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE;
import static org.thingsboard.server.common.data.CacheConstants.RESOURCE_INFO_CACHE;
import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTINGS_CACHE;
@ -55,7 +54,11 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
clearCacheByName(RESOURCE_INFO_CACHE);
break;
case "3.6.3":
log.info("Clearing cache to upgrade from version 3.6.3 to 3.7.0");
log.info("Clearing cache to upgrade from version 3.6.3 to 3.6.4");
clearAll();
break;
case "3.6.4":
log.info("Clearing cache to upgrade from version 3.6.4 to 3.7.0");
clearAll();
break;
default:

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

@ -26,12 +26,15 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
@ -42,6 +45,7 @@ import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@Service
@ -67,6 +71,13 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
DeviceConnectivityConfiguration connectivityConfiguration;
@Autowired
private CustomerDao customerDao;
@Autowired
private CustomerService customerService;
@Override
public void updateData(String fromVersion) throws Exception {
switch (fromVersion) {
@ -74,11 +85,60 @@ public class DefaultDataUpdateService implements DataUpdateService {
log.info("Updating data from version 3.6.0 to 3.6.1 ...");
migrateDeviceConnectivity();
break;
case "3.6.4":
log.info("Updating data from version 3.6.4 to 3.7.0 ...");
updateCustomersWithTheSameTitle();
break;
default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
}
}
private void updateCustomersWithTheSameTitle() {
var customers = new ArrayList<Customer>();
new PageDataIterable<>(pageLink ->
customerDao.findCustomersWithTheSameTitle(pageLink), DEFAULT_PAGE_SIZE
).forEach(customers::add);
if (customers.isEmpty()) {
return;
}
var firstCustomer = customers.get(0);
var titleToDeduplicate = firstCustomer.getTitle();
var tenantIdToDeduplicate = firstCustomer.getTenantId();
int duplicateCounter = 1;
for (int i = 1; i < customers.size(); i++) {
var currentCustomer = customers.get(i);
if (currentCustomer.getTitle().equals(titleToDeduplicate) && currentCustomer.getTenantId().equals(tenantIdToDeduplicate)) {
duplicateCounter++;
String currentTitle = currentCustomer.getTitle();
String newTitle = currentTitle + " " + duplicateCounter;
try {
Optional<Customer> customerOpt = customerService.findCustomerByTenantIdAndTitle(tenantIdToDeduplicate, newTitle);
if (customerOpt.isPresent()) {
// fallback logic: customer with title 'currentTitle + " " + duplicateCounter;' might be another duplicate.
newTitle = currentTitle + "_" + currentCustomer.getId();
}
} catch (Exception e) {
log.trace("Failed to find customer with title due to: ", e);
// fallback logic: customer with title 'currentTitle + " " + duplicateCounter;' might be another duplicate.
newTitle = currentTitle + "_" + currentCustomer.getId();
}
currentCustomer.setTitle(newTitle);
try {
customerService.saveCustomer(currentCustomer);
} catch (Exception e) {
log.error("[{}] Failed to update customer with id and title: {}, oldTitle: {}, due to: ",
currentCustomer.getTenantId(), newTitle, currentTitle, e);
}
continue;
}
titleToDeduplicate = currentCustomer.getTitle();
tenantIdToDeduplicate = currentCustomer.getTenantId();
duplicateCounter = 1;
}
}
private void migrateDeviceConnectivity() {
if (adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "connectivity") == null) {
AdminSettings connectivitySettings = new AdminSettings();

3
application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java

@ -105,9 +105,10 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data, unreadCount);
} catch (FirebaseMessagingException e) {
MessagingErrorCode errorCode = e.getMessagingErrorCode();
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) {
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT || errorCode == MessagingErrorCode.SENDER_ID_MISMATCH) {
validTokens.remove(token);
userService.removeMobileSession(recipient.getTenantId(), token);
log.debug("[{}][{}] Removed invalid FCM token due to {} {} ({})", recipient.getTenantId(), recipient.getId(), errorCode, e.getMessage(), token);
continue;
}
throw new RuntimeException("Failed to send message via FCM: " + e.getMessage(), e);

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentTrigger;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java

@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Notif
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentTrigger;
import org.thingsboard.server.dao.entity.EntityService;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm
import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EdgeCommunicationFailureNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EdgeConnectionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java

@ -27,7 +27,7 @@ import org.thingsboard.server.dao.entity.EntityCountService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
@Service
@RequiredArgsConstructor

4
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;
@ -47,7 +47,7 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
return false;
}
}
if (!partitionService.resolve(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId()).isMyPartition()) {
if (!partitionService.isMyPartition(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId())) {
return false;
}

53
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.notification.info.TaskProcessingFailureNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.config.TaskProcessingFailureNotificationRuleTriggerConfig;
@Service
public class TaskProcessingFailureTriggerProcessor implements NotificationRuleTriggerProcessor<TaskProcessingFailureTrigger, TaskProcessingFailureNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(TaskProcessingFailureTrigger trigger, TaskProcessingFailureNotificationRuleTriggerConfig triggerConfig) {
return true;
}
@Override
public TaskProcessingFailureNotificationInfo constructNotificationInfo(TaskProcessingFailureTrigger trigger) {
HousekeeperTask task = trigger.getTask();
return TaskProcessingFailureNotificationInfo.builder()
.tenantId(task.getTenantId())
.entityId(task.getEntityId())
.taskType(task.getTaskType())
.taskDescription(task.getDescription())
.error(StringUtils.truncate(ExceptionUtils.getStackTrace(trigger.getError()), 1024))
.attempt(trigger.getAttempt())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE;
}
}

109
application/src/main/java/org/thingsboard/server/service/queue/consumer/QueueConsumerManager.java

@ -0,0 +1,109 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue.consumer;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
@Slf4j
public class QueueConsumerManager<M extends TbQueueMsg> {
private final String name;
private final MsgPackProcessor<M> msgPackProcessor;
private final long pollInterval;
private final ExecutorService consumerExecutor;
private final String threadPrefix;
@Getter
private final TbQueueConsumer<M> consumer;
private volatile boolean stopped;
@Builder
public QueueConsumerManager(String name, MsgPackProcessor<M> msgPackProcessor,
long pollInterval, Supplier<TbQueueConsumer<M>> consumerCreator,
ExecutorService consumerExecutor, String threadPrefix) {
this.name = name;
this.pollInterval = pollInterval;
this.msgPackProcessor = msgPackProcessor;
this.consumerExecutor = consumerExecutor;
this.threadPrefix = threadPrefix;
this.consumer = consumerCreator.get();
}
public void subscribe() {
consumer.subscribe();
}
public void subscribe(Set<TopicPartitionInfo> partitions) {
consumer.subscribe(partitions);
}
public void launch() {
log.info("[{}] Launching consumer", name);
consumerExecutor.submit(() -> {
if (threadPrefix != null) {
ThingsBoardThreadFactory.addThreadNamePrefix(threadPrefix);
}
try {
consumerLoop(consumer);
} catch (Throwable e) {
log.error("Failure in consumer loop", e);
}
log.info("[{}] Consumer stopped", name);
});
}
private void consumerLoop(TbQueueConsumer<M> consumer) {
while (!stopped && !consumer.isStopped()) {
try {
List<M> msgs = consumer.poll(pollInterval);
if (msgs.isEmpty()) {
continue;
}
msgPackProcessor.process(msgs, consumer);
} catch (Exception e) {
if (!consumer.isStopped()) {
log.warn("Failed to process messages from queue", e);
try {
Thread.sleep(pollInterval);
} catch (InterruptedException interruptedException) {
log.trace("Failed to wait until the server has capacity to handle new requests", interruptedException);
}
}
}
}
}
public void stop() {
log.debug("[{}] Stopping consumer", name);
stopped = true;
consumer.unsubscribe();
}
public interface MsgPackProcessor<M extends TbQueueMsg> {
void process(List<M> msgs, TbQueueConsumer<M> consumer) throws Exception;
}
}

33
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java

@ -108,34 +108,40 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
return entity;
}
@Override
public <E extends ExportableEntity<I>, I extends EntityId> E findDefaultEntityByTenantId(TenantId tenantId, EntityType entityType) {
ExportableEntityDao<I, E> dao = getExportableEntityDao(entityType);
if (dao == null) {
return null;
}
return dao.findDefaultEntityByTenantId(tenantId.getId());
}
@Override
public <E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) {
ExportableEntityDao<I, E> dao = getExportableEntityDao(entityType);
if (dao != null) {
return dao.findByTenantId(tenantId.getId(), pageLink);
} else {
return new PageData<>();
if (dao == null) {
return PageData.emptyPageData();
}
return dao.findByTenantId(tenantId.getId(), pageLink);
}
@Override
public <I extends EntityId> PageData<I> findEntitiesIdsByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) {
ExportableEntityDao<I, ?> dao = getExportableEntityDao(entityType);
if (dao != null) {
return dao.findIdsByTenantId(tenantId.getId(), pageLink);
} else {
return new PageData<>();
if (dao == null) {
return PageData.emptyPageData();
}
return dao.findIdsByTenantId(tenantId.getId(), pageLink);
}
@Override
public <I extends EntityId> I getExternalIdByInternal(I internalId) {
ExportableEntityDao<I, ?> dao = getExportableEntityDao(internalId.getEntityType());
if (dao != null) {
return dao.getExternalIdByInternal(internalId);
} else {
if (dao == null) {
return null;
}
return dao.getExternalIdByInternal(internalId);
}
private boolean belongsToTenant(HasId<? extends EntityId> entity, TenantId tenantId) {
@ -147,10 +153,7 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
public <I extends EntityId> void removeById(TenantId tenantId, I id) {
EntityType entityType = id.getEntityType();
EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
if (entityService == null) {
throw new IllegalArgumentException("Unsupported entity type " + entityType);
}
entityService.deleteEntity(tenantId, id);
entityService.deleteEntity(tenantId, id, false);
}
private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) {

2
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java

@ -33,6 +33,8 @@ public interface ExportableEntitiesService {
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name);
<E extends ExportableEntity<I>, I extends EntityId> E findDefaultEntityByTenantId(TenantId tenantId, EntityType entityType);
<E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink);
<I extends EntityId> PageData<I> findEntitiesIdsByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink);

2
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.sync.ie.exporting.impl;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;

21
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasDefaultOption;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -73,7 +74,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
@Autowired
@Lazy
private ExportableEntitiesService exportableEntitiesService;
private ExportableEntitiesService entitiesService;
@Autowired
private RelationService relationService;
@Autowired
@ -274,22 +275,30 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
@SuppressWarnings("unchecked")
protected E findExistingEntity(EntitiesImportCtx ctx, E entity, IdProvider idProvider) {
return (E) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(ctx.getTenantId(), entity.getId()))
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entity.getId())))
return (E) Optional.ofNullable(entitiesService.findEntityByTenantIdAndExternalId(ctx.getTenantId(), entity.getId()))
.or(() -> Optional.ofNullable(entitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entity.getId())))
.or(() -> {
if (ctx.isFindExistingByName()) {
return Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndName(ctx.getTenantId(), getEntityType(), entity.getName()));
return Optional.ofNullable(entitiesService.findEntityByTenantIdAndName(ctx.getTenantId(), getEntityType(), entity.getName()));
} else {
return Optional.empty();
}
})
.or(() -> {
if (entity instanceof HasDefaultOption hasDefaultOption) {
if (hasDefaultOption.isDefault()) {
return Optional.ofNullable(entitiesService.findDefaultEntityByTenantId(ctx.getTenantId(), getEntityType()));
}
}
return Optional.empty();
})
.orElse(null);
}
@SuppressWarnings("unchecked")
private <ID extends EntityId> HasId<ID> findInternalEntity(TenantId tenantId, ID externalId) {
return (HasId<ID>) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId))
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId)))
return (HasId<ID>) Optional.ofNullable(entitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId))
.or(() -> Optional.ofNullable(entitiesService.findEntityByTenantIdAndId(tenantId, externalId)))
.orElseThrow(() -> new MissingEntityException(externalId));
}

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;

1
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -56,7 +56,6 @@ public class AlarmsCleanUpService {
private final TenantService tenantService;
private final AlarmDao alarmDao;
private final AlarmService alarmService;
private final RelationService relationService;
private final EntityActionService entityActionService;
private final PartitionService partitionService;
private final TbTenantProfileCache tenantProfileCache;

7
application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java

@ -50,7 +50,8 @@ public class AuditLogsCleanUpService extends AbstractCleanUpService {
@Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.audit_logs.checking_interval_ms})}",
fixedDelayString = "${sql.ttl.audit_logs.checking_interval_ms}")
public void cleanUp() {
long auditLogsExpTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
long auditLogsExpTime = getCurrentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
log.debug("cleanup {}", auditLogsExpTime);
if (isSystemTenantPartitionMine()) {
auditLogDao.cleanUpAuditLogs(auditLogsExpTime);
} else {
@ -58,4 +59,8 @@ public class AuditLogsCleanUpService extends AbstractCleanUpService {
}
}
public long getCurrentTimeMillis() {
return System.currentTimeMillis();
}
}

2
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -24,7 +24,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;

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

@ -505,6 +505,12 @@ cache:
assets:
timeToLiveInMinutes: "${CACHE_SPECS_ASSETS_TTL:1440}" # Asset cache TTL
maxSize: "${CACHE_SPECS_ASSETS_MAX_SIZE:10000}" # 0 means the cache is disabled
customers:
timeToLiveInMinutes: "${CACHE_SPECS_CUSTOMERS_TTL:1440}" # Customer cache TTL
maxSize: "${CACHE_SPECS_CUSTOMERS_MAX_SIZE:10000}" # 0 means the cache is disabled
users:
timeToLiveInMinutes: "${CACHE_SPECS_USERS_TTL:1440}" # User cache TTL
maxSize: "${CACHE_SPECS_USERS_MAX_SIZE:10000}" # 0 means the cache is disabled
entityViews:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_VIEWS_TTL:1440}" # Entity view cache TTL
maxSize: "${CACHE_SPECS_ENTITY_VIEWS_MAX_SIZE:10000}" # 0 means the cache is disabled
@ -1445,6 +1451,16 @@ queue:
# tb_rule_engine.sq:
# - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
tb_housekeeper:
# Consumer properties for Housekeeper tasks topic
- key: max.poll.records
# Amount of records to be returned in a single poll. For Housekeeper tasks topic, we should consume messages (tasks) one by one
value: "${TB_QUEUE_KAFKA_HOUSEKEEPER_MAX_POLL_RECORDS:1}"
tb_housekeeper.reprocessing:
# Consumer properties for Housekeeper reprocessing topic
- key: max.poll.records
# Amount of records to be returned in a single poll. For Housekeeper reprocessing topic, we should consume messages (tasks) one by one
value: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_MAX_POLL_RECORDS:1}"
other-inline: "${TB_QUEUE_KAFKA_OTHER_PROPERTIES:}" # In this section you can specify custom parameters (semicolon separated) for Kafka consumer/producer/admin # Example "metrics.recording.level:INFO;metrics.sample.window.ms:30000"
other: # DEPRECATED. In this section, you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside
# - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
@ -1466,6 +1482,10 @@ queue:
ota-updates: "${TB_QUEUE_KAFKA_OTA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
# Kafka properties for Version Control topic
version-control: "${TB_QUEUE_KAFKA_VC_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
# Kafka properties for Housekeeper tasks topic
housekeeper: "${TB_QUEUE_KAFKA_HOUSEKEEPER_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
# Kafka properties for Housekeeper reprocessing topic; retention.ms is set to 90 days; partitions is set to 1 since only one reprocessing service is running at a time
housekeeper-reprocessing: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_TOPIC_PROPERTIES:retention.ms:7776000000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
consumer-stats:
# Prints lag between consumer group offset and last messages offset in Kafka topics
enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
@ -1621,6 +1641,29 @@ queue:
enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
# Statistics printing interval for Core microservices
print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}"
housekeeper:
# Topic name for Housekeeper tasks
topic: "${TB_HOUSEKEEPER_TOPIC:tb_housekeeper}"
# Topic name for Housekeeper tasks to be reprocessed
reprocessing-topic: "${TB_HOUSEKEEPER_REPROCESSING_TOPIC:tb_housekeeper.reprocessing}"
# Poll interval for topics related to Housekeeper
poll-interval-ms: "${TB_HOUSEKEEPER_POLL_INTERVAL_MS:500}"
# Timeout in milliseconds for task processing. Tasks that fail to finish on time will be submitted for reprocessing
task-processing-timeout-ms: "${TB_HOUSEKEEPER_TASK_PROCESSING_TIMEOUT_MS:120000}"
# Comma-separated list of task types that shouldn't be processed. Available task types:
# DELETE_ATTRIBUTES, DELETE_TELEMETRY (both DELETE_LATEST_TS and DELETE_TS_HISTORY will be disabled),
# DELETE_LATEST_TS, DELETE_TS_HISTORY, DELETE_EVENTS, DELETE_ALARMS, UNASSIGN_ALARMS
disabled-task-types: "${TB_HOUSEKEEPER_DISABLED_TASK_TYPES:}"
# Delay in milliseconds between tasks reprocessing
task-reprocessing-delay-ms: "${TB_HOUSEKEEPER_TASK_REPROCESSING_DELAY_MS:3000}"
# Maximum amount of task reprocessing attempts. After exceeding, the task will be dropped
max-reprocessing-attempts: "${TB_HOUSEKEEPER_MAX_REPROCESSING_ATTEMPTS:10}"
stats:
# Enable/disable statistics for Housekeeper
enabled: "${TB_HOUSEKEEPER_STATS_ENABLED:true}"
# Statistics printing interval for Housekeeper
print-interval-ms: "${TB_HOUSEKEEPER_STATS_PRINT_INTERVAL_MS:60000}"
vc:
# Default topic name for Kafka, RabbitMQ, etc.
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
@ -1730,3 +1773,7 @@ management:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
health:
elasticsearch:
# Enable the org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator.doHealthCheck
enabled: "false"

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

@ -25,7 +25,6 @@ import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.hibernate.exception.ConstraintViolationException;
@ -114,9 +113,11 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
@ -229,7 +230,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
private TbTenantProfileService tbTenantProfileService;
@Autowired
public TimeseriesService tsService;
protected TimeseriesService tsService;
@Autowired
protected AttributesService attributesService;
@Autowired
protected DefaultActorService actorService;
@ -240,6 +244,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
@SpyBean
protected MailService mailService;
@Autowired
protected InMemoryStorage storage;
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
@ -282,7 +289,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
Tenant tenant = new Tenant();
tenant.setTitle(TEST_TENANT_NAME);
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
tenantProfileId = savedTenant.getTenantProfileId();
@ -360,22 +367,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
}
private void deleteTenant(TenantId tenantId) {
int status = 0;
int retries = 0;
while (status != HttpStatus.SC_OK && retries < CLEANUP_TENANT_RETRIES_COUNT) {
retries++;
try {
status = doDelete("/api/tenant/" + tenantId.getId().toString())
.andReturn().getResponse().getStatus();
if (status != HttpStatus.SC_OK) {
log.warn("Tenant deletion failed, tenantId: {}", tenantId.getId().toString());
Thread.sleep(1000L);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
protected void deleteTenant(TenantId tenantId) {
try {
doDelete("/api/tenant/" + tenantId.getId()).andExpect(status().isOk());
} catch (Exception e) {
throw new RuntimeException(e);
}
Awaitility.await("all tasks processed").atMost(60, TimeUnit.SECONDS).during(300, TimeUnit.MILLISECONDS)
.until(() -> storage.getLag("tb_housekeeper") == 0);
}
private List<Tenant> getAllTenants() throws Exception {
@ -428,7 +427,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle(TEST_DIFFERENT_TENANT_NAME);
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
savedDifferentTenant = saveTenant(tenant);
differentTenantId = savedDifferentTenant.getId();
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
@ -438,6 +437,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
savedDifferentTenantUser = createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD);
}
protected Tenant saveTenant(Tenant tenant) throws Exception {
return doPost("/api/tenant", tenant, Tenant.class);
}
protected void loginDifferentCustomer() throws Exception {
if (savedDifferentCustomer != null) {
login(savedDifferentCustomer.getEmail(), CUSTOMER_USER_PASSWORD);

115
application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java

@ -53,9 +53,8 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -584,7 +583,7 @@ public class AlarmControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
beforeAssignmentTs = System.currentTimeMillis();
long afterAssignmentTs = System.currentTimeMillis();
loginSysAdmin();
@ -592,12 +591,12 @@ public class AlarmControllerTest extends AbstractControllerTest {
loginDifferentTenant();
foundAlarm = Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class), Objects::nonNull);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
AlarmInfo alarmInfo = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(alarmInfo);
Assert.assertNull(alarmInfo.getAssigneeId());
Assert.assertTrue(alarmInfo.getAssignTs() >= afterAssignmentTs);
});
}
@Test
@ -631,71 +630,16 @@ public class AlarmControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
beforeAssignmentTs = System.currentTimeMillis();
long afterAssignmentTs = System.currentTimeMillis();
doDelete("/api/user/" + savedUser.getId().getId()).andExpect(status().isOk());
foundAlarm = Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class), Objects::nonNull);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
}
@Test
public void testUnassignAlarmOnCustomerRemoving() throws Exception {
createDifferentTenantCustomer();
loginDifferentTenant();
User user = new User();
user.setAuthority(Authority.CUSTOMER_USER);
user.setTenantId(tenantId);
user.setCustomerId(differentTenantCustomerId);
user.setEmail("customerForAssign@thingsboard.org");
User savedUser = createUser(user, "password");
Device device = createDevice("Different customer device", "default", "differentTenantTest");
Device assignedDevice = doPost("/api/customer/" + differentTenantCustomerId.getId()
+ "/device/" + device.getId().getId(), Device.class);
Assert.assertEquals(differentTenantCustomerId, assignedDevice.getCustomerId());
Alarm alarm = Alarm.builder()
.type(TEST_ALARM_TYPE)
.tenantId(savedDifferentTenant.getId())
.customerId(differentTenantCustomerId)
.originator(device.getId())
.severity(AlarmSeverity.MAJOR)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
AlarmId alarmId = alarm.getId();
alarm = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(alarm);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarmId.getId() + "/assign/" + savedUser.getId().getId()).andExpect(status().isOk());
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
beforeAssignmentTs = System.currentTimeMillis();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + differentTenantCustomerId.getId()).andExpect(status().isOk());
foundAlarm = Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class), Objects::nonNull);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
AlarmInfo alarmInfo = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(alarmInfo);
Assert.assertNull(alarmInfo.getAssigneeId());
Assert.assertTrue(alarmInfo.getAssignTs() >= afterAssignmentTs);
});
}
@Test
@ -1003,27 +947,20 @@ public class AlarmControllerTest extends AbstractControllerTest {
types.remove(0);
}
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
})
.getData()
.stream()
.map(EntitySubtype::getType)
.collect(Collectors.toList());
Assert.assertEquals(types.size(), foundTypes.size());
Assert.assertEquals(types, foundTypes);
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
List<String> actualTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {})
.getData().stream().map(EntitySubtype::getType).toList();
Assert.assertEquals(types.size(), actualTypes.size());
Assert.assertEquals(types, actualTypes);
});
doDelete("/api/device/" + devices.get(0).getId()).andExpect(status().isOk());
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
})
.getData()
.stream()
.map(EntitySubtype::getType)
.sorted()
.collect(Collectors.toList());
Assert.assertTrue(foundTypes.isEmpty());
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
List<String> actualTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {})
.getData().stream().map(EntitySubtype::getType).sorted().toList();
Assert.assertTrue(actualTypes.isEmpty());
});
}
}

24
application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java

@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData;
@ -55,8 +56,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -87,7 +90,7 @@ public class AssetControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -104,8 +107,7 @@ public class AssetControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -312,8 +314,9 @@ public class AssetControllerTest extends AbstractControllerTest {
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
AlarmId alarmId = alarm.getId();
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarmId, AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
@ -324,9 +327,11 @@ public class AssetControllerTest extends AbstractControllerTest {
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Asset", assetIdStr))));
doGet("/api/alarm/info/" + alarm.getId())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarm.getId().getId().toString()))));
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
doGet("/api/alarm/info/" + alarmId)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarmId.getId().toString()))));
});
}
@Test
@ -548,7 +553,7 @@ public class AssetControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -582,8 +587,7 @@ public class AssetControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test

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

@ -82,7 +82,7 @@ public class AssetProfileControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -99,8 +99,7 @@ public class AssetProfileControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

87
application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java

@ -15,7 +15,9 @@
*/
package org.thingsboard.server.controller;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -28,6 +30,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.AuditLogId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
@ -38,19 +41,21 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.service.ttl.AuditLogsCleanUpService;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.time.DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@DaoSqlTest
public class AuditLogControllerTest extends AbstractControllerTest {
@ -61,7 +66,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
private AuditLogDao auditLogDao;
@SpyBean
private SqlPartitioningRepository partitioningRepository;
@Autowired
@SpyBean
private AuditLogsCleanUpService auditLogsCleanUpService;
@Value("#{${sql.audit_logs.partition_size} * 60 * 60 * 1000}")
@ -75,7 +80,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -92,13 +97,12 @@ public class AuditLogControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
public void testAuditLogs() throws Exception {
for (int i = 0; i < 178; i++) {
for (int i = 0; i < 11; i++) {
Device device = new Device();
device.setName("Device" + i);
device.setType("default");
@ -106,7 +110,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(23);
TimePageLink pageLink = new TimePageLink(5);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs?",
@ -118,10 +122,10 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
Assert.assertEquals(11, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(23);
pageLink = new TimePageLink(5);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
new TypeReference<PageData<AuditLog>>() {
@ -132,10 +136,10 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
Assert.assertEquals(11, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(23);
pageLink = new TimePageLink(5);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
new TypeReference<PageData<AuditLog>>() {
@ -146,7 +150,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
Assert.assertEquals(11, loadedAuditLogs.size());
}
@Test
@ -155,13 +159,13 @@ public class AuditLogControllerTest extends AbstractControllerTest {
device.setName("Device name");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
for (int i = 0; i < 178; i++) {
for (int i = 0; i < 11; i++) {
savedDevice.setName("Device name" + i);
doPost("/api/device", savedDevice, Device.class);
}
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(23);
TimePageLink pageLink = new TimePageLink(5);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
@ -173,54 +177,62 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(179, loadedAuditLogs.size());
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
}
@Test
public void whenSavingNewAuditLog_thenCheckAndCreatePartitionIfNotExists() {
public void whenSavingNewAuditLog_thenCheckAndCreatePartitionIfNotExists() throws ParseException {
long entityTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2024-01-01T01:43:11Z").getTime();
reset(partitioningRepository);
AuditLog auditLog = createAuditLog(ActionType.LOGIN, tenantAdminUserId);
AuditLog auditLog = createAuditLog(ActionType.LOGIN, tenantAdminUserId, entityTs);
verify(partitioningRepository).createPartitionIfNotExists(eq("audit_log"), eq(auditLog.getCreatedTime()), eq(partitionDurationInMs));
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).singleElement().satisfies(partitionStartTs -> {
assertThat(partitionStartTs).isEqualTo(partitioningRepository.calculatePartitionStartTime(auditLog.getCreatedTime(), partitionDurationInMs));
});
assertThat(partitions).contains(partitioningRepository.calculatePartitionStartTime(auditLog.getCreatedTime(), partitionDurationInMs));
}
@Test
public void whenCleaningUpAuditLogsByTtl_thenDropOldPartitions() {
long oldAuditLogTs = LocalDate.of(2020, 10, 1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
long partitionStartTs = partitioningRepository.calculatePartitionStartTime(oldAuditLogTs, partitionDurationInMs);
public void whenCleaningUpAuditLogsByTtl_thenDropOldPartitions() throws ParseException {
final long oldAuditLogTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2020-10-01T00:00:00Z").getTime();
final long currentTimeMillis = oldAuditLogTs + TimeUnit.SECONDS.toMillis(auditLogsTtlInSec) * 2;
final long partitionStartTs = partitioningRepository.calculatePartitionStartTime(oldAuditLogTs, partitionDurationInMs);
partitioningRepository.createPartitionIfNotExists("audit_log", oldAuditLogTs, partitionDurationInMs);
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).contains(partitionStartTs);
willReturn(currentTimeMillis).given(auditLogsCleanUpService).getCurrentTimeMillis();
auditLogsCleanUpService.cleanUp();
partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).doesNotContain(partitionStartTs);
assertThat(partitions).allSatisfy(partitionsStart -> {
assertThat(partitions).as("partitions cleared").doesNotContain(partitionStartTs);
assertThat(partitions).as("only newer partitions left").allSatisfy(partitionsStart -> {
long partitionEndTs = partitionsStart + partitionDurationInMs;
assertThat(partitionEndTs).isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(auditLogsTtlInSec));
assertThat(partitionEndTs).isGreaterThan(currentTimeMillis - TimeUnit.SECONDS.toMillis(auditLogsTtlInSec));
});
}
@Test
public void whenSavingAuditLogAndPartitionSaveErrorOccurred_thenSaveAuditLogAnyway() throws Exception {
// creating partition bigger than sql.audit_logs.partition_size
partitioningRepository.createPartitionIfNotExists("audit_log", System.currentTimeMillis(), TimeUnit.DAYS.toMillis(7));
long entityTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2022-04-29T07:43:11Z").getTime();
//the partition 7 days is overlapping default partition size 1 day, use in the far past to not affect other tests
partitioningRepository.createPartitionIfNotExists("audit_log", entityTs, TimeUnit.DAYS.toMillis(7));
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).size().isOne();
partitioningRepository.cleanupPartitionsCache("audit_log", System.currentTimeMillis(), 0);
log.warn("entityTs [{}], fetched partitions {}", entityTs, partitions);
assertThat(partitions).contains(ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2022-04-28T00:00:00Z").getTime());
partitioningRepository.cleanupPartitionsCache("audit_log", entityTs, 0);
assertDoesNotThrow(() -> {
// expecting partition overlap error on partition save
createAuditLog(ActionType.LOGIN, tenantAdminUserId);
createAuditLog(ActionType.LOGIN, tenantAdminUserId, entityTs);
});
assertThat(partitioningRepository.fetchPartitions("audit_log")).isEqualTo(partitions);
assertThat(partitioningRepository.fetchPartitions("audit_log"))
.contains(ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2022-04-28T00:00:00Z").getTime());;
}
private AuditLog createAuditLog(ActionType actionType, EntityId entityId) {
private AuditLog createAuditLog(ActionType actionType, EntityId entityId, long entityTs) {
AuditLog auditLog = new AuditLog();
auditLog.setTenantId(tenantId);
auditLog.setCustomerId(null);
@ -228,6 +240,13 @@ public class AuditLogControllerTest extends AbstractControllerTest {
auditLog.setEntityId(entityId);
auditLog.setUserName(tenantAdmin.getEmail());
auditLog.setActionType(actionType);
if (entityTs > 0) {
UUID uuid = Uuids.startOf(entityTs);
auditLog.setId(new AuditLogId(uuid));
auditLog.setCreatedTime(Uuids.unixTimestamp(uuid));
}
return auditLogDao.save(tenantId, auditLog);
}

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

@ -167,4 +167,10 @@ public class AuthControllerTest extends AbstractControllerTest {
.andExpect(jsonPath("$.message",
is("Password must be no more than 72 characters in length.")));
}
@Test
public void testGetPageWithoutRedirect() throws Exception {
doGet("/login").andExpect(status().isOk());
doGet("/home").andExpect(status().isOk());
}
}

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

@ -47,7 +47,7 @@ public class ComponentDescriptorControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -64,8 +64,7 @@ public class ComponentDescriptorControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

46
application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java

@ -85,7 +85,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -104,8 +104,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -154,7 +153,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgError)));
customer.setTenantId(savedTenant.getId());
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -165,7 +164,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -176,7 +175,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -187,7 +186,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -198,7 +197,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -209,7 +208,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@ -293,7 +292,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@ -353,7 +352,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
}
@Test
public void testFindCustomersByTitle() throws Exception {
public void testFindCustomersWithTitleAsTextSearch() throws Exception {
TenantId tenantId = savedTenant.getId();
String title1 = "Customer title 1";
@ -425,6 +424,31 @@ public class CustomerControllerTest extends AbstractControllerTest {
Assert.assertEquals(0, pageData.getData().size());
}
@Test
public void testFindCustomerByTitle() throws Exception {
Customer customer = new Customer();
customer.setTitle("My customer");
Mockito.reset(tbClusterService, auditLogService);
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
testNotifyEntityAllOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(),
new CustomerId(CustomerId.NULL_UUID), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
Assert.assertNotNull(savedCustomer);
Assert.assertNotNull(savedCustomer.getId());
Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
Customer foundCustomer = doGet("/api/tenant/customers?customerTitle=" + savedCustomer.getTitle(), Customer.class);
Assert.assertEquals(foundCustomer, savedCustomer);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testDeleteCustomerWithDeleteRelationsOk() throws Exception {
CustomerId customerId = createCustomer("Customer for Test WithRelationsOk").getId();

20
application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java

@ -77,7 +77,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -94,8 +94,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -166,7 +165,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test
public void testSaveDashboardWithEmptyTitle() throws Exception {
Dashboard dashboard = new Dashboard();
String msgError = "Dashboard title " + msgErrorShouldBeSpecified;;
String msgError = "Dashboard title " + msgErrorShouldBeSpecified;
Mockito.reset(tbClusterService, auditLogService);
@ -196,7 +195,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(),
savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
ActionType.UPDATED, assignedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle());
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
@ -239,8 +238,8 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertTrue(publicCustomer.isPublic());
testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(),
savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
ActionType.UPDATED, assignedDashboard .getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle());
savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
ActionType.UPDATED, assignedDashboard.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle());
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(publicCustomer.toShortCustomerInfo()));
@ -283,7 +282,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -321,8 +320,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test
@ -482,7 +480,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity, cntEntity, cntEntity*2);
ActionType.ADDED, cntEntity, cntEntity, cntEntity * 2);
List<DashboardInfo> loadedDashboards = new ArrayList<>();
PageLink pageLink = new PageLink(21);

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

@ -142,7 +142,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -187,8 +187,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -84,6 +84,7 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
@ -132,7 +133,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -151,8 +152,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -687,7 +687,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -721,9 +721,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
testNotificationUpdateGatewayNever();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test
@ -914,7 +912,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Device(), new Device(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity,1);
ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, 1);
testNotificationUpdateGatewayNever();
}
@ -1319,7 +1317,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
Tenant savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedDifferentTenant = saveTenant(tenant);
Assert.assertNotNull(savedDifferentTenant);
User user = new User();
@ -1380,8 +1378,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorNoFound("Device", savedAnotherDevice.getId().getId().toString()))));
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedDifferentTenant.getId());
}
@Test
@ -1545,17 +1542,24 @@ public class DeviceControllerTest extends AbstractControllerTest {
Device savedDevice = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
//check server attribute value
List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<>() {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
Map<String, Object> actualAttribute = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<List<Map<String, Object>>>() {}).stream()
.filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(attributeValue, actualAttribute.get("value"));
});
Map<String, Object> serverAttribute = values.stream().filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(attributeValue, serverAttribute.get("value"));
//update server attribute value
String newAttributeValue = "testValue2";
JsonNode content = JacksonUtil.toJsonNode("{\"DATA\": \"" + newAttributeValue + "\"}");
doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice.getUuidId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
Map<String, Object> actualAttribute = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<List<Map<String, Object>>>() {}).stream()
.filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(newAttributeValue, actualAttribute.get("value"));
});
//reimport devices
String deviceName2 = "secondDevice";
@ -1567,19 +1571,19 @@ public class DeviceControllerTest extends AbstractControllerTest {
doPostWithTypedResponse("/api/device/bulk_import", request, new TypeReference<>() {});
Device savedDevice2 = doGet("/api/tenant/devices?deviceName=" + deviceName2, Device.class);
//check attribute value was not changed after reimport
List<Map<String, Object>> values2 = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<>() {
});
Map<String, Object> retrievedServerAttribute2 = values2.stream().filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(newAttributeValue, retrievedServerAttribute2.get("value"));
//check attribute for second device
List<Map<String, Object>> values3 = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice2.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<>() {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
Map<String, Object> actualAttribute = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice2.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<List<Map<String, Object>>>() {}).stream()
.filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(attributeValue2, actualAttribute.get("value"));
});
Map<String, Object> retrievedServerAttribute3 = values3.stream().filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(attributeValue2, retrievedServerAttribute3.get("value"));
//check attribute value was not changed after reimport
Map<String, Object> actualAttribute = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<List<Map<String, Object>>>() {}).stream()
.filter(att -> att.get("key").equals("DATA")).findFirst().get();
Assert.assertEquals(newAttributeValue, actualAttribute.get("value"));
}
private Device createDevice(String name) {

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

@ -93,7 +93,7 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -110,8 +110,7 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -361,7 +361,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -394,8 +394,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test

75
application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java

@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge;
@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.edge.EdgeEventDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
@ -89,21 +90,22 @@ public class EdgeEventControllerTest extends AbstractControllerTest {
Edge edge = constructEdge("TestEdge", "default");
edge = doPost("/api/edge", edge, Edge.class);
final EdgeId edgeId = edge.getId();
awaitForEdgeTemplateRootRuleChainToAssignToEdge(edgeId);
// simulate edge activation
ObjectNode attributes = JacksonUtil.newObjectNode();
attributes.put("active", true);
doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + DataConstants.SERVER_SCOPE, attributes);
doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + AttributeScope.SERVER_SCOPE, attributes);
Device device = constructDevice("TestDevice", "default");
Device savedDevice = doPost("/api/device", device, Device.class);
final EdgeId edgeId = edge.getId();
doPost("/api/edge/" + edgeId.toString() + "/device/" + savedDevice.getId().toString(), Device.class);
doPost("/api/edge/" + edgeId + "/device/" + savedDevice.getId(), Device.class);
Asset asset = constructAsset("TestAsset", "default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
doPost("/api/edge/" + edgeId.toString() + "/asset/" + savedAsset.getId().toString(), Asset.class);
doPost("/api/edge/" + edgeId + "/asset/" + savedAsset.getId(), Asset.class);
EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE);
@ -114,31 +116,12 @@ public class EdgeEventControllerTest extends AbstractControllerTest {
awaitForNumberOfEdgeEvents(edgeId, 3);
List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.DEVICE)); // TestDevice
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.ASSET)); // TestAsset
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.DEVICE));
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.ASSET));
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.RELATION));
Assert.assertTrue(edgeEvents.isEmpty());
}
private boolean popEdgeEvent(List<EdgeEvent> edgeEvents, EdgeEventType edgeEventType) {
for (EdgeEvent edgeEvent : edgeEvents) {
if (edgeEventType.equals(edgeEvent.getType())) {
edgeEvents.remove(edgeEvent);
return true;
}
}
return false;
}
private void awaitForNumberOfEdgeEvents(EdgeId edgeId, int expectedNumber) {
Awaitility.await()
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
return edgeEvents.size() == expectedNumber;
});
}
@Test
public void saveEdgeEvent_thenCreatePartitionIfNotExist() {
reset(partitioningRepository);
@ -167,12 +150,46 @@ public class EdgeEventControllerTest extends AbstractControllerTest {
});
}
private boolean popEdgeEvent(List<EdgeEvent> edgeEvents, EdgeEventType edgeEventType) {
for (EdgeEvent edgeEvent : edgeEvents) {
if (edgeEventType.equals(edgeEvent.getType())) {
edgeEvents.remove(edgeEvent);
return true;
}
}
return false;
}
private void awaitForNumberOfEdgeEvents(EdgeId edgeId, int expectedNumber) {
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
return edgeEvents.size() == expectedNumber;
});
}
private List<EdgeEvent> findEdgeEvents(EdgeId edgeId) throws Exception {
return doGetTypedWithTimePageLink("/api/edge/" + edgeId.toString() + "/events?",
return doGetTypedWithTimePageLink("/api/edge/" + edgeId + "/events?",
new TypeReference<PageData<EdgeEvent>>() {
}, new TimePageLink(10)).getData();
}
private void awaitForEdgeTemplateRootRuleChainToAssignToEdge(EdgeId edgeId) {
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
List<RuleChain> ruleChains = getEdgeRuleChains(edgeId);
return ruleChains.size() == 1;
});
}
private List<RuleChain> getEdgeRuleChains(EdgeId edgeId) throws Exception {
return doGetTypedWithTimePageLink("/api/edge/" + edgeId + "/ruleChains?",
new TypeReference<PageData<RuleChain>>() {
}, new TimePageLink(10)).getData();
}
private Device constructDevice(String name, String type) {
Device device = new Device();
device.setName(name);

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

@ -52,6 +52,7 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.security.Authority;
@ -83,7 +84,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -99,9 +100,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -644,4 +643,166 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
Assert.assertEquals(97, count.longValue());
}
@Test
public void testFindDevicesCountByOwnerNameAndOwnerType() throws Exception {
loginTenantAdmin();
int numOfDevices = 8;
for (int i = 0; i < numOfDevices; i++) {
Device device = new Device();
String name = "Device" + i;
device.setName(name);
device.setType("default");
Device savedDevice = doPost("/api/device?accessToken=" + name, device, Device.class);
JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}");
doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice.getUuidId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("");
KeyFilter activeAlarmTimeFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 5);
KeyFilter activeAlarmTimeToLongFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 30);
KeyFilter tenantOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", TEST_TENANT_NAME);
KeyFilter wrongOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", "wrongName");
KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
KeyFilter customerOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "CUSTOMER");
// all devices with ownerName = TEST TENANT
EntityCountQuery query = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
checkEntitiesCount(query, numOfDevices);
// all devices with ownerName = TEST TENANT
EntityCountQuery activeAlarmTimeToLongQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeToLongFilter, tenantOwnerNameFilter));
checkEntitiesCount(activeAlarmTimeToLongQuery, 0);
// all devices with wrong ownerName
EntityCountQuery wrongTenantNameQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
checkEntitiesCount(wrongTenantNameQuery, 0);
// all devices with owner type = TENANT
EntityCountQuery tenantEntitiesQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
checkEntitiesCount(tenantEntitiesQuery, numOfDevices);
// all devices with owner type = CUSTOMER
EntityCountQuery customerEntitiesQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
checkEntitiesCount(customerEntitiesQuery, 0);
}
@Test
public void testFindDevicesByOwnerNameAndOwnerType() throws Exception {
loginTenantAdmin();
int numOfDevices = 3;
for (int i = 0; i < numOfDevices; i++) {
Device device = new Device();
String name = "Device" + i;
device.setName(name);
device.setType("default");
Device savedDevice = doPost("/api/device?accessToken=" + name, device, Device.class);
JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}");
doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice.getUuidId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("");
KeyFilter activeAlarmTimeFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 5);
KeyFilter tenantOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", TEST_TENANT_NAME);
KeyFilter wrongOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", "wrongName");
KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
KeyFilter customerOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "CUSTOMER");
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "ownerName"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "ownerType"));
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmActiveTime"));
// all devices with ownerName = TEST TENANT
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
checkEntitiesByQuery(query, numOfDevices, TEST_TENANT_NAME, "TENANT");
// all devices with wrong ownerName
EntityDataQuery wrongTenantNameQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
checkEntitiesByQuery(wrongTenantNameQuery, 0, null, null);
// all devices with owner type = TENANT
EntityDataQuery tenantEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
checkEntitiesByQuery(tenantEntitiesQuery, numOfDevices, TEST_TENANT_NAME, "TENANT");
// all devices with owner type = CUSTOMER
EntityDataQuery customerEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
checkEntitiesByQuery(customerEntitiesQuery, 0, null, null);
}
private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception {
Awaitility.await()
.alias("data by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == expectedNumOfDevices;
});
if (expectedNumOfDevices == 0) {
return;
}
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
Assert.assertEquals(expectedNumOfDevices, loadedEntities.size());
for (int i = 0; i < expectedNumOfDevices; i++) {
var entity = loadedEntities.get(i);
String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
String ownerName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerName", new TsValue(0, "Invalid")).getValue();
String ownerType = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerType", new TsValue(0, "Invalid")).getValue();
String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
Assert.assertEquals("Device" + i, name);
Assert.assertEquals( expectedOwnerName, ownerName);
Assert.assertEquals( expectedOwnerType, ownerType);
Assert.assertEquals("1" + i, alarmActiveTime);
}
}
private void checkEntitiesCount(EntityCountQuery query, int expectedNumOfDevices) {
Awaitility.await()
.alias("count by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
var count = doPost("/api/entitiesQuery/count", query, Integer.class);
return count == expectedNumOfDevices;
});
}
private KeyFilter getEntityFieldStringEqualToKeyFilter(String keyName, String value) {
KeyFilter tenantOwnerNameFilter = new KeyFilter();
tenantOwnerNameFilter.setKey(new EntityKey(EntityKeyType.ENTITY_FIELD, keyName));
StringFilterPredicate ownerNamePredicate = new StringFilterPredicate();
ownerNamePredicate.setValue(FilterPredicateValue.fromString(value));
ownerNamePredicate.setOperation(StringFilterPredicate.StringOperation.EQUAL);
tenantOwnerNameFilter.setPredicate(ownerNamePredicate);
return tenantOwnerNameFilter;
}
private KeyFilter getServerAttributeNumericGreaterThanKeyFilter(String attribute, int value) {
KeyFilter numericFilter = new KeyFilter();
numericFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, attribute));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(FilterPredicateValue.fromDouble(value));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
numericFilter.setPredicate(predicate);
return numericFilter;
}
}

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

@ -72,7 +72,7 @@ public class EntityRelationControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -93,8 +93,7 @@ public class EntityRelationControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

20
application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java

@ -275,10 +275,9 @@ public class EntityViewControllerTest extends AbstractControllerTest {
}
@Test
public void testAssignAndUnAssignedEntityViewToCustomer() throws Exception {
public void testAssignAndUnassignEntityViewToCustomer() throws Exception {
EntityView view = getNewSavedEntityView("Test entity view");
Customer savedCustomer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class);
view.setCustomerId(savedCustomer.getId());
Mockito.reset(tbClusterService, auditLogService);
@ -304,17 +303,17 @@ public class EntityViewControllerTest extends AbstractControllerTest {
ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED,
foundView.getId().getId().toString(), foundView.getCustomerId().getId().toString(), savedCustomer.getTitle());
EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(ModelConstants.NULL_UUID, unAssignedView.getCustomerId().getId());
EntityView unassignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(ModelConstants.NULL_UUID, unassignedView.getCustomerId().getId());
foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId());
testBroadcastEntityStateChangeEventTime(foundView.getId(), foundView.getTenantId(), 1);
testNotifyAssignUnassignEntityAllOneTime(unAssignedView, savedView.getId(), savedView.getId(),
tenantId, savedView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL,
testNotifyAssignUnassignEntityAllOneTime(unassignedView, unassignedView.getId(), unassignedView.getId(),
tenantId, savedCustomer.getId(), tenantAdminUserId, TENANT_ADMIN_EMAIL,
ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED,
assignedView.getId().getId().toString(), savedView.getCustomerId().getId().toString(), savedCustomer.getTitle());
assignedView.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle());
}
@Test
@ -370,7 +369,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant2 = getNewTenant("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -398,8 +397,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test
@ -431,7 +429,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL,
ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, cntEntity, cntEntity,
cntEntity*2, 3);
cntEntity * 2, 3);
}
@Test

4
application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java

@ -103,7 +103,7 @@ public class HomePageApiTest extends AbstractControllerTest {
for (int i = 0; i < 100; i++) {
Tenant tenant = new Tenant();
tenant.setTitle("tenant" + i);
tenants.add(doPost("/api/tenant", tenant, Tenant.class));
tenants.add(saveTenant(tenant));
}
EntityTypeFilter ef = new EntityTypeFilter();
@ -115,7 +115,7 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(initialCount + 100, update.getCount());
for (Tenant tenant : tenants) {
doDelete("/api/tenant/" + tenant.getId().toString());
deleteTenant(tenant.getId());
}
}

7
application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java

@ -72,7 +72,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -94,8 +94,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -339,7 +338,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest {
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new OtaPackageInfo(), new OtaPackageInfo(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity, 0, (cntEntity*2 - startIndexSaveData));
ActionType.ADDED, cntEntity, 0, (cntEntity * 2 - startIndexSaveData));
List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(24);

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

@ -49,7 +49,7 @@ public class RpcControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -66,8 +66,7 @@ public class RpcControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
private Device createDefaultDevice() {

11
application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java

@ -82,7 +82,7 @@ public class RuleChainControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -99,8 +99,7 @@ public class RuleChainControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -147,9 +146,9 @@ public class RuleChainControllerTest extends AbstractControllerTest {
int currentVersion = annotation.version();
String oldConfig = "{\"attrMapping\":{\"serialNumber\":\"sn\"}," +
"\"relationsQuery\":{\"direction\":\"FROM\",\"maxLevel\":1," +
"\"filters\":[{\"relationType\":\"Contains\",\"entityTypes\":[]}]," +
"\"fetchLastLevelOnly\":false},\"telemetry\":false}";
"\"relationsQuery\":{\"direction\":\"FROM\",\"maxLevel\":1," +
"\"filters\":[{\"relationType\":\"Contains\",\"entityTypes\":[]}]," +
"\"fetchLastLevelOnly\":false},\"telemetry\":false}";
TbGetRelatedDataNodeConfiguration defaultConfiguration = new TbGetRelatedDataNodeConfiguration().defaultConfiguration();
String newConfig = JacksonUtil.toString(defaultConfiguration);

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

@ -73,7 +73,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -90,8 +90,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

49
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -140,7 +140,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService);
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
Assert.assertNotNull(savedTenant.getId());
Assert.assertTrue(savedTenant.getCreatedTime() > 0);
@ -149,14 +149,13 @@ public class TenantControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.CREATED, 1);
savedTenant.setTitle("My new tenant");
doPost("/api/tenant", savedTenant, Tenant.class);
saveTenant(savedTenant);
Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class);
Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle());
testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.UPDATED, 1);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.DELETED, 1);
}
@ -181,12 +180,11 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class);
Assert.assertNotNull(foundTenant);
Assert.assertEquals(savedTenant, foundTenant);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -194,12 +192,11 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
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());
deleteTenant(savedTenant.getId());
}
@Test
@ -237,11 +234,10 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
String tenantIdStr = savedTenant.getId().getId().toString();
doDelete("/api/tenant/" + tenantIdStr)
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
doGet("/api/tenant/" + tenantIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Tenant", tenantIdStr))));
@ -264,8 +260,7 @@ public class TenantControllerTest extends AbstractControllerTest {
for (int i = 0; i < cntEntity; i++) {
Tenant tenant = new Tenant();
tenant.setTitle("Tenant" + i);
createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
createFutures.add(executor.submit(() -> saveTenant(tenant)));
}
tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS));
@ -308,8 +303,7 @@ public class TenantControllerTest extends AbstractControllerTest {
String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title);
createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
createFutures.add(executor.submit(() -> saveTenant(tenant)));
}
List<Tenant> tenantsTitle1 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS);
@ -323,8 +317,7 @@ public class TenantControllerTest extends AbstractControllerTest {
String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title);
createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
createFutures.add(executor.submit(() -> saveTenant(tenant)));
}
List<Tenant> tenantsTitle2 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS);
@ -396,7 +389,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Tenant" + i);
createFutures.add(executor.submit(() ->
new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default")));
new TenantInfo(saveTenant(tenant), "Default")));
}
tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS));
@ -448,7 +441,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Isolated tenant");
tenant = doPost("/api/tenant", tenant, Tenant.class);
tenant = saveTenant(tenant);
User tenantUser = new User();
tenantUser.setAuthority(Authority.TENANT_ADMIN);
@ -477,7 +470,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
tenant.setTenantProfileId(tenantProfile.getId());
doPost("/api/tenant", tenant, Tenant.class);
saveTenant(tenant);
login(username, password);
@ -507,7 +500,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile2 = doPost("/api/tenantProfile", tenantProfile2, TenantProfile.class);
tenant.setTenantProfileId(tenantProfile2.getId());
doPost("/api/tenant", tenant, Tenant.class);
saveTenant(tenant);
login(username, password);
@ -549,7 +542,7 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
tenant.setTenantProfileId(null);
doPost("/api/tenant", tenant, Tenant.class);
saveTenant(tenant);
login(username, password);
for (Queue queue : foundTenantQueues) {
@ -559,7 +552,7 @@ public class TenantControllerTest extends AbstractControllerTest {
}
loginSysAdmin();
doDelete("/api/tenant/" + tenant.getId().getId().toString()).andExpect(status().isOk());
deleteTenant(tenant.getId());
}
@Test
@ -576,7 +569,7 @@ public class TenantControllerTest extends AbstractControllerTest {
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
loginDifferentTenant();
@ -661,7 +654,7 @@ public class TenantControllerTest extends AbstractControllerTest {
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
await().atMost(30, TimeUnit.SECONDS)
.until(() -> {
@ -716,7 +709,7 @@ public class TenantControllerTest extends AbstractControllerTest {
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull();

39
application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java

@ -75,7 +75,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
savedTenantProfile.setName("New tenant profile");
doPost("/api/tenantProfile", savedTenantProfile, TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName());
testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.UPDATED, 1);
@ -100,7 +100,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
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);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertNotNull(foundTenantProfile);
Assert.assertEquals(savedTenantProfile, foundTenantProfile);
}
@ -110,7 +110,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
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);
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());
@ -129,7 +129,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
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", TenantProfile.class);
TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString() + "/default", TenantProfile.class);
Assert.assertNotNull(defaultTenantProfile);
EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
Assert.assertNotNull(foundDefaultTenantProfile);
@ -176,7 +176,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant with tenant profile");
tenant.setTenantProfileId(savedTenantProfile.getId());
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Mockito.reset(tbClusterService);
@ -186,8 +186,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventNeverTenantProfile();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -214,7 +213,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
List<TenantProfile> tenantProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantProfile> pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
tenantProfiles.addAll(pageData.getData());
@ -222,8 +221,8 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService);
int cntEntity = 28;
for (int i=0;i<28;i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
for (int i = 0; i < 28; i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i);
tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
}
@ -233,7 +232,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
pageLink = new PageLink(17);
do {
pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {}, pageLink);
loadedTenantProfiles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -256,7 +255,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<PageData<TenantProfile>>(){}, pageLink);
new TypeReference<PageData<TenantProfile>>() {}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
@ -269,13 +268,13 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
List<TenantProfile> tenantProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantProfile> tenantProfilePageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {}, 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);
for (int i = 0; i < 28; i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i);
tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
}
@ -284,7 +283,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
PageData<EntityInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
new TypeReference<PageData<EntityInfo>>(){}, pageLink);
new TypeReference<PageData<EntityInfo>>() {}, pageLink);
loadedTenantProfileInfos.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -308,7 +307,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
new TypeReference<PageData<EntityInfo>>(){}, pageLink);
new TypeReference<PageData<EntityInfo>>() {}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@ -354,14 +353,14 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
ArgumentMatcher<TenantProfile> matcherTenantProfile = cntTime == 1 ? argument -> argument.equals(tenantProfile) :
argument -> argument.getClass().equals(TenantProfile.class);
if (ComponentLifecycleEvent.DELETED.equals(event)) {
Mockito.verify(tbClusterService, times( cntTime)).onTenantProfileDelete(Mockito.argThat( matcherTenantProfile),
Mockito.verify(tbClusterService, times(cntTime)).onTenantProfileDelete(Mockito.argThat(matcherTenantProfile),
Mockito.isNull());
testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant()));
} else {
Mockito.verify(tbClusterService, times( cntTime)).onTenantProfileChange(Mockito.argThat(matcherTenantProfile),
Mockito.verify(tbClusterService, times(cntTime)).onTenantProfileChange(Mockito.argThat(matcherTenantProfile),
Mockito.isNull());
TenantProfileId tenantProfileIdId = cntTime == 1 ? tenantProfile.getId() : (TenantProfileId) createEntityId_NULL_UUID(tenantProfile);
testBroadcastEntityStateChangeEventTime(tenantProfileIdId, null, cntTime);
testBroadcastEntityStateChangeEventTime(tenantProfileIdId, null, cntTime);
}
Mockito.reset(tbClusterService);
}

19
application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java

@ -279,7 +279,7 @@ public class UserControllerTest extends AbstractControllerTest {
user.setTenantId(tenantId);
user.setEmail(TENANT_ADMIN_EMAIL);
String msgError = "User with email '" + TENANT_ADMIN_EMAIL + "' already present in database";
String msgError = "User with email '" + TENANT_ADMIN_EMAIL + "' already present in database!";
doPost("/api/user", user)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
@ -380,7 +380,7 @@ public class UserControllerTest extends AbstractControllerTest {
//here created a new tenant despite already created on AbstractWebTest and then delete the tenant properly on the last line
Tenant tenant = new Tenant();
tenant.setTitle("My tenant with many admins");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId();
@ -422,8 +422,7 @@ public class UserControllerTest extends AbstractControllerTest {
assertThat(tenantAdmins).as("admins list size").hasSameSizeAs(loadedTenantAdmins);
assertThat(tenantAdmins).as("admins list content").isEqualTo(loadedTenantAdmins);
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
pageLink = new PageLink(33);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
@ -669,7 +668,7 @@ public class UserControllerTest extends AbstractControllerTest {
List<UserId> expectedCustomerUserIds = new ArrayList<>();
expectedCustomerUserIds.add(customerUserId);
for (int i = 0; i < 45; i++) {
User customerUser = createCustomerUser( customerId);
User customerUser = createCustomerUser(customerId);
customerUser.setEmail(email + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org");
User user = doPost("/api/user", customerUser, User.class);
expectedCustomerUserIds.add(user.getId());
@ -750,7 +749,7 @@ public class UserControllerTest extends AbstractControllerTest {
String email = "testEmail1";
for (int i = 0; i < 45; i++) {
User customerUser = createCustomerUser( customerId);
User customerUser = createCustomerUser(customerId);
customerUser.setEmail(email + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org");
doPost("/api/user", customerUser, User.class);
}
@ -993,8 +992,8 @@ public class UserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator);
@ -1046,8 +1045,8 @@ public class UserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator);

24
application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java

@ -24,7 +24,6 @@ import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
@ -53,7 +52,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -70,8 +69,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -127,10 +125,10 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
doDelete("/api/widgetType/"+savedWidgetType.getId().getId().toString())
doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/widgetType/"+savedWidgetType.getId().getId().toString())
doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString())
.andExpect(status().isNotFound());
}
@ -173,7 +171,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
List<WidgetType> widgetTypes = new ArrayList<>();
for (int i=0;i<89;i++) {
for (int i = 0; i < 89; i++) {
WidgetTypeDetails widgetType = new WidgetTypeDetails();
widgetType.setName("Widget Type " + i);
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
@ -184,7 +182,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
doPost("/api/widgetsBundle/" + widgetsBundle.getId().getId().toString() + "/widgetTypes", widgetTypeIds);
List<WidgetType> loadedWidgetTypes = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
Collections.sort(widgetTypes, idComparator);
Collections.sort(loadedWidgetTypes, idComparator);
@ -194,12 +192,12 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
loginCustomerUser();
List<WidgetType> loadedWidgetTypesCustomer = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
Collections.sort(loadedWidgetTypesCustomer, idComparator);
Assert.assertEquals(widgetTypes, loadedWidgetTypesCustomer);
List<WidgetTypeDetails> customerLoadedWidgetTypesDetails = doGetTyped("/api/widgetTypesDetails?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
List<WidgetType> widgetTypesFromDetailsListCustomer = customerLoadedWidgetTypesDetails.stream().map(WidgetType::new).collect(Collectors.toList());
Collections.sort(widgetTypesFromDetailsListCustomer, idComparator);
Assert.assertEquals(widgetTypesFromDetailsListCustomer, loadedWidgetTypes);
@ -207,12 +205,12 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
loginSysAdmin();
List<WidgetType> sysAdminLoadedWidgetTypes = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
Collections.sort(sysAdminLoadedWidgetTypes, idComparator);
Assert.assertEquals(widgetTypes, sysAdminLoadedWidgetTypes);
List<WidgetTypeDetails> sysAdminLoadedWidgetTypesDetails = doGetTyped("/api/widgetTypesDetails?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
List<WidgetType> widgetTypesFromDetailsListSysAdmin = sysAdminLoadedWidgetTypesDetails.stream().map(WidgetType::new).collect(Collectors.toList());
Collections.sort(widgetTypesFromDetailsListSysAdmin, idComparator);
Assert.assertEquals(widgetTypesFromDetailsListSysAdmin, loadedWidgetTypes);
@ -225,7 +223,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
WidgetType foundWidgetType = doGet("/api/widgetType?fqn={fqn}",
WidgetType.class, "tenant."+savedWidgetType.getFqn());
WidgetType.class, "tenant." + savedWidgetType.getFqn());
Assert.assertNotNull(foundWidgetType);
Assert.assertEquals(new WidgetType(savedWidgetType), foundWidgetType);
}

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

@ -53,7 +53,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -70,8 +70,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

4
application/src/test/java/org/thingsboard/server/edge/TenantEdgeTest.java

@ -40,7 +40,7 @@ public class TenantEdgeTest extends AbstractEdgeTest {
// updated edge tenant
savedTenant.setTitle("Updated Title for Tenant Edge Test");
edgeImitator.expectMessageAmount(2); // expect tenant and tenant profile update msg
savedTenant = doPost("/api/tenant", savedTenant, Tenant.class);
savedTenant = saveTenant(savedTenant);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<TenantUpdateMsg> tenantUpdateMsgOpt = edgeImitator.findMessageByType(TenantUpdateMsg.class);
Assert.assertTrue(tenantUpdateMsgOpt.isPresent());
@ -61,7 +61,7 @@ public class TenantEdgeTest extends AbstractEdgeTest {
TenantProfile tenantProfile = createTenantProfile();
savedTenant.setTenantProfileId(tenantProfile.getId());
edgeImitator.expectMessageAmount(2); // expect tenant and tenant profile update msg
savedTenant = doPost("/api/tenant", savedTenant, Tenant.class);
savedTenant = saveTenant(savedTenant);
Assert.assertTrue(edgeImitator.waitForMessages());
tenantUpdateMsgOpt = edgeImitator.findMessageByType(TenantUpdateMsg.class);
Assert.assertTrue(tenantUpdateMsgOpt.isPresent());

7
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java

@ -27,9 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.util.TbMsgSource;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.rule.engine.util.TbMsgSource;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
@ -62,7 +62,6 @@ import java.util.List;
import java.util.stream.Collectors;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Valerii Sosliuk
@ -103,7 +102,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId());
@ -121,7 +120,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
public void afterTest() throws Exception {
loginSysAdmin();
if (savedTenant != null) {
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
}

2
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -173,7 +173,7 @@ public class DefaultTbAlarmServiceTest {
User user = new User();
user.setEmail("testEmail@gmail.com");
user.setId(new UserId(UUID.randomUUID()));
service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user, System.currentTimeMillis());
service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user.getId(), user.getTitle(), System.currentTimeMillis());
ObjectNode commentNode = JacksonUtil.newObjectNode();
commentNode.put("subtype", "ASSIGN");

478
application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java

@ -0,0 +1,478 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
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.id.UserId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateDao;
import org.thingsboard.server.gen.transport.TransportProtos.HousekeeperTaskProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.service.housekeeper.processor.TsHistoryDeletionTaskProcessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@TestPropertySource(properties = {
"transport.http.enabled=true",
"queue.core.housekeeper.task-reprocessing-delay-ms=2000",
"queue.core.housekeeper.poll-interval-ms=1000",
"queue.core.housekeeper.max-reprocessing-attempts=5"
})
public class HousekeeperServiceTest extends AbstractControllerTest {
@SpyBean
private HousekeeperService housekeeperService;
@SpyBean
private HousekeeperReprocessingService housekeeperReprocessingService;
@Autowired
private EventService eventService;
@Autowired
private TimeseriesService timeseriesService;
@Autowired
private AttributesService attributesService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private AlarmService alarmService;
@Autowired
private AlarmDao alarmDao;
@Autowired
private RelationService relationService;
@Autowired
private ApiUsageStateDao apiUsageStateDao;
@Autowired
private EntityServiceRegistry entityServiceRegistry;
@SpyBean
private TsHistoryDeletionTaskProcessor tsHistoryDeletionTaskProcessor;
private TenantId tenantId;
private static final String TELEMETRY_KEY = "telemetry1";
private static final String ATTRIBUTE_KEY = "_attribute1";
private static final String KV_VALUE = "ewfewfwef";
@Before
public void setUp() throws Exception {
loginTenantAdmin();
this.tenantId = super.tenantId;
}
@After
public void tearDown() throws Exception {
}
@Test
public void whenDeviceIsDeleted_thenCleanUpRelatedData() throws Exception {
Device device = createDevice("test", "test");
createRelatedData(device.getId());
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(device.getId());
});
}
@Test
public void whenRuleChainIsDeleted_thenCleanUpRelatedData() throws Exception {
RuleChainMetaData ruleChainMetaData = createRuleChain();
RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId();
RuleNodeId ruleNode1Id = ruleChainMetaData.getNodes().get(0).getId();
RuleNodeId ruleNode2Id = ruleChainMetaData.getNodes().get(1).getId();
createRelatedData(ruleChainId);
createRelatedData(ruleNode1Id);
createRelatedData(ruleNode2Id);
doDelete("/api/ruleChain/" + ruleChainId).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(ruleNode1Id);
verifyNoRelatedData(ruleNode2Id);
verifyNoRelatedData(ruleChainId);
});
}
@Test
public void whenUserIsDeleted_thenCleanUpRelatedData() throws Exception {
Device device = createDevice("test", "test");
UserId userId = customerUserId;
createRelatedData(userId);
Alarm alarm = Alarm.builder()
.type("test")
.tenantId(tenantId)
.originator(device.getId())
.severity(AlarmSeverity.MAJOR)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmId alarmId = alarm.getId();
alarm = doPost("/api/alarm/" + alarmId + "/assign/" + userId, "", Alarm.class);
assertThat(alarm.getAssigneeId()).isEqualTo(userId);
assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, new PageLink(100)).getData()).isNotEmpty();
doDelete("/api/user/" + userId).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(userId);
assertThat(alarmService.findAlarmById(tenantId, alarmId).getAssigneeId()).isNull();
});
}
@Test
public void whenDeviceIsDeleted_thenDeleteAllAlarms() throws Exception {
Device device = createDevice("test", "test");
for (int i = 1; i <= 1000; i++) {
createAlarm(device.getId());
}
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoAlarms(device.getId());
});
}
@Test
public void whenTenantIsDeleted_thenDeleteAllEntitiesAndCleanUpRelatedData() throws Exception {
loginDifferentTenant();
tenantId = differentTenantId;
createRelatedData(tenantId);
createDifferentTenantCustomer();
createRelatedData(differentTenantCustomerId);
loginDifferentTenant();
List<DeviceId> devices = new ArrayList<>();
for (int i = 1; i <= 300; i++) {
Device device = createDevice("test" + i, "test" + i);
devices.add(device.getId());
}
DeviceId firstDevice = devices.get(0);
createRelatedData(firstDevice);
DeviceId lastDevice = devices.get(devices.size() - 1);
createRelatedData(lastDevice);
Asset asset = createAsset();
createRelatedData(asset.getId());
createRelation(firstDevice, asset.getId());
createAlarm(firstDevice, asset.getId());
RuleChainMetaData ruleChainMetaData = createRuleChain();
RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId();
RuleNodeId ruleNode1Id = ruleChainMetaData.getNodes().get(0).getId();
RuleNodeId ruleNode2Id = ruleChainMetaData.getNodes().get(1).getId();
createRelatedData(ruleChainId);
createRelatedData(ruleNode1Id);
createRelatedData(ruleNode2Id);
UserId userId = savedDifferentTenantUser.getId();
createRelatedData(userId);
ApiUsageState tenantApiUsageState = apiUsageStateDao.findApiUsageStateByEntityId(differentTenantId);
loginSysAdmin();
deleteDifferentTenant();
await().atMost(60, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> {
for (DeviceId deviceId : devices) {
verifyNoRelatedData(deviceId);
}
verifyNoRelatedData(asset.getId());
verifyNoRelatedData(ruleNode1Id);
verifyNoRelatedData(ruleNode2Id);
verifyNoRelatedData(ruleChainId);
verifyNoRelatedData(userId);
verifyNoRelatedData(differentTenantCustomerId);
verifyNoRelatedData(tenantApiUsageState.getId());
verifyNoRelatedData(tenantId);
});
}
@Test
public void whenTaskProcessingFails_thenReprocess() throws Exception {
TimeoutException error = new TimeoutException("Test timeout");
doThrow(error).when(tsHistoryDeletionTaskProcessor).process(any());
Device device = createDevice("test", "test");
createRelatedData(device.getId());
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
int attempts = 2;
await().atMost(30, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> {
for (int i = 0; i <= attempts; i++) {
int attempt = i;
verify(housekeeperReprocessingService).submitForReprocessing(argThat(getTaskMatcher(device.getId(), HousekeeperTaskType.DELETE_TS_HISTORY,
task -> task.getAttempt() == attempt)), argThat(e -> e.getMessage().equals(error.getMessage())));
}
});
assertThat(getTimeseriesHistory(device.getId())).isNotEmpty();
doCallRealMethod().when(tsHistoryDeletionTaskProcessor).process(any());
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(getTimeseriesHistory(device.getId())).isEmpty();
});
}
@Test
public void whenReprocessingAttemptsExceeded_thenDropTheTask() throws Exception {
TimeoutException error = new TimeoutException("Test timeout");
doThrow(error).when(tsHistoryDeletionTaskProcessor).process(any());
Device device = createDevice("test", "test");
createRelatedData(device.getId());
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
int maxAttempts = 5;
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
for (int i = 1; i <= maxAttempts; i++) {
verifyTaskProcessing(device.getId(), HousekeeperTaskType.DELETE_TS_HISTORY, i);
}
});
Mockito.clearInvocations(housekeeperService);
doCallRealMethod().when(tsHistoryDeletionTaskProcessor).process(any());
TimeUnit.SECONDS.sleep(2);
verify(housekeeperService, never()).processTask(argThat(getTaskMatcher(device.getId(), HousekeeperTaskType.DELETE_TS_HISTORY, null)));
}
private void verifyTaskProcessing(EntityId entityId, HousekeeperTaskType taskType, int expectedAttempt) throws Exception {
verify(housekeeperService).processTask(argThat(getTaskMatcher(entityId, taskType, task -> task.getAttempt() == expectedAttempt)));
}
private ArgumentMatcher<ToHousekeeperServiceMsg> getTaskMatcher(EntityId entityId, HousekeeperTaskType taskType,
Predicate<HousekeeperTaskProto> additionalCheck) {
return msg -> {
HousekeeperTask task = JacksonUtil.fromString(msg.getTask().getValue(), HousekeeperTask.class);
return task.getEntityId().equals(entityId) && task.getTaskType() == taskType && (additionalCheck == null || additionalCheck.test(msg.getTask()));
};
}
private void createRelatedData(EntityId entityId) throws Exception {
createTelemetry(entityId);
for (AttributeScope scope : AttributeScope.values()) {
createAttribute(entityId, scope, scope + ATTRIBUTE_KEY);
}
createEvent(entityId);
}
private void verifyNoRelatedData(EntityId entityId) throws Exception {
assertThat(entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()).findEntity(tenantId, entityId)).isEmpty();
assertThat(getLatestTelemetry(entityId)).isNull();
assertThat(getTimeseriesHistory(entityId)).isEmpty();
for (AttributeScope scope : AttributeScope.values()) {
assertThat(attributesService.findAll(tenantId, entityId, scope).get()).isEmpty();
}
assertThat(getEvents(entityId)).isEmpty();
assertThat(alarmDao.findEntityAlarmRecordsByEntityId(tenantId, entityId)).isEmpty();
verifyNoAlarms(entityId);
assertThat(relationService.findByTo(tenantId, entityId, RelationTypeGroup.COMMON)).isEmpty();
assertThat(relationService.findByFrom(tenantId, entityId, RelationTypeGroup.COMMON)).isEmpty();
}
private void verifyNoAlarms(EntityId entityId) {
assertThat(alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, 0, null, 10)).isEmpty();
}
private void createAttribute(EntityId entityId, AttributeScope scope, String key) throws Exception {
attributesService.save(tenantId, entityId, scope, new BaseAttributeKvEntry(System.currentTimeMillis(), new StringDataEntry(key, KV_VALUE))).get();
}
private void createTelemetry(EntityId entityId) throws Exception {
timeseriesService.save(tenantId, entityId, new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(TELEMETRY_KEY, KV_VALUE))).get();
}
private void createEvent(EntityId entityId) {
LifecycleEvent event = LifecycleEvent.builder()
.tenantId(tenantId)
.entityId(entityId.getId())
.serviceId("test")
.lcEventType("test")
.success(true)
.build();
eventService.saveAsync(event);
await().atMost(10, TimeUnit.SECONDS)
.until(() -> !getEvents(entityId).isEmpty());
}
private void createRelation(DeviceId to, AssetId from) {
EntityRelation relation = new EntityRelation(from, to, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON);
relationService.saveRelation(tenantId, relation);
}
private void createAlarm(DeviceId deviceId, EntityId propagatedEntityId) {
Alarm alarm = doPost("/api/alarm", Alarm.builder()
.tenantId(tenantId)
.originator(deviceId)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm for " + deviceId)
.propagate(true)
.build(), Alarm.class);
List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(tenantId, alarm.getId());
assertThat(entityAlarms).anyMatch(entityAlarm -> entityAlarm.getEntityId().equals(deviceId) && entityAlarm.getAlarmType().equals(alarm.getType()));
assertThat(entityAlarms).anyMatch(entityAlarm -> entityAlarm.getEntityId().equals(propagatedEntityId) && entityAlarm.getAlarmType().equals(alarm.getType()));
assertThat(alarmService.findAlarmIdsByOriginatorId(tenantId, deviceId, 0, null, 10)).isNotEmpty();
}
private void createAlarm(DeviceId deviceId) {
Alarm alarm = doPost("/api/alarm", Alarm.builder()
.tenantId(tenantId)
.originator(deviceId)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm for " + deviceId + " " + RandomStringUtils.randomAlphabetic(10))
.build(), Alarm.class);
assertThat(alarmService.findAlarmIdsByOriginatorId(tenantId, deviceId, 0, null, 10)).isNotEmpty();
}
private TsKvEntry getLatestTelemetry(EntityId entityId) throws Exception {
return timeseriesService.findLatest(tenantId, entityId, HousekeeperServiceTest.TELEMETRY_KEY).get().orElse(null);
}
private List<TsKvEntry> getTimeseriesHistory(EntityId entityId) throws Exception {
return timeseriesService.findAll(tenantId, entityId, List.of(new BaseReadTsKvQuery(HousekeeperServiceTest.TELEMETRY_KEY, 0, System.currentTimeMillis(), 10, "DESC"))).get();
}
private List<EventInfo> getEvents(EntityId entityId) {
return eventService.findEvents(tenantId, entityId, EventType.LC_EVENT, new TimePageLink(100)).getData()
.stream().filter(event -> Optional.ofNullable(event.getBody()).map(body -> body.get("event"))
.map(JsonNode::asText).orElse("").equals("test"))
.collect(Collectors.toList());
}
private Asset createAsset() {
Asset asset = new Asset();
asset.setName("test");
asset.setType("test");
return doPost("/api/asset", asset, Asset.class);
}
private RuleChainMetaData createRuleChain() {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setName("Test");
ruleChain.setType(RuleChainType.CORE);
ruleChain.setDebugMode(true);
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
ruleChain = ruleChainService.saveRuleChain(ruleChain);
RuleChainId ruleChainId = ruleChain.getId();
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChainId);
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
metaData.setFirstNodeIndex(0);
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS);
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity());
return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId);
}
}

23
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -323,8 +323,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
deleteDifferentTenant();
assertThat(notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(1)).getTotalElements())
.isZero();
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(1)).getTotalElements())
.isZero();
});
}
@Test
@ -523,18 +525,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRequestStats() throws Exception {
wsClient.subscribeForUnreadNotifications(10);
wsClient.waitForReply(true);
wsClient.registerWaitForUpdate();
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)", NotificationDeliveryMethod.WEB);
wsClient.waitForUpdate();
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId());
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)", NotificationDeliveryMethod.WEB);
NotificationRequestStats stats = awaitNotificationRequest(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
doDelete("/api/user/mobile/session").andExpect(status().isOk());
notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test", NotificationDeliveryMethod.MOBILE_APP);
stats = awaitNotificationRequest(notificationRequest.getId());
assertThat(stats.getErrors().get(NotificationDeliveryMethod.MOBILE_APP)).hasSize(1);
assertThat(stats.getTotalErrors()).hasValue(1);
}
@Test

7
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java

@ -106,8 +106,11 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
}
}
} else if (updateType == CmdUpdateType.NOTIFICATIONS_COUNT) {
lastCountUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsCountUpdate.class);
unreadCount = lastCountUpdate.getTotalUnreadCount();
UnreadNotificationsCountUpdate countUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsCountUpdate.class);
if (lastCountUpdate == null || countUpdate.getSequenceNumber() > lastCountUpdate.getSequenceNumber()) {
lastCountUpdate = countUpdate;
unreadCount = lastCountUpdate.getTotalUnreadCount();
}
}
super.onMessage(s);
}

6
application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java

@ -38,8 +38,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@ -148,7 +150,9 @@ public class NotificationTargetApiTest extends AbstractNotificationApiTest {
assertThat(notificationTargetDao.findByTenantIdAndSupportedNotificationTypeAndPageLink(differentTenantId, NotificationType.GENERAL, new PageLink(10)).getData()).isNotEmpty();
deleteDifferentTenant();
assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty();
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty();
});
}
@Test

16
application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java

@ -51,7 +51,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class BaseTbResourceServiceTest extends AbstractControllerTest {
@ -129,7 +128,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant);
@ -147,8 +146,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -397,7 +395,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = doPost("/api/tenant", tenant, Tenant.class);
tenant = saveTenant(tenant);
TenantId tenantId = tenant.getId();
@ -441,8 +439,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
}
@Test
@ -451,7 +448,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = doPost("/api/tenant", tenant, Tenant.class);
tenant = saveTenant(tenant);
TenantId tenantId = tenant.getId();
@ -518,8 +515,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
}
}

6
application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java

@ -50,8 +50,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest {
@ -77,7 +75,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -93,7 +91,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

94
application/src/test/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManagerTest.java

@ -40,6 +40,8 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import java.util.Optional;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.any;
@ -53,13 +55,13 @@ import static org.mockito.Mockito.never;
public class DefaultRuleEngineDeviceStateManagerTest {
@Mock
private static DeviceStateService deviceStateServiceMock;
private DeviceStateService deviceStateServiceMock;
@Mock
private static TbCallback tbCallbackMock;
private TbCallback tbCallbackMock;
@Mock
private static TbClusterService clusterServiceMock;
private TbClusterService clusterServiceMock;
@Mock
private static TbQueueMsgMetadata metadataMock;
private TbQueueMsgMetadata metadataMock;
@Mock
private TbServiceInfoProvider serviceInfoProviderMock;
@ -67,9 +69,9 @@ public class DefaultRuleEngineDeviceStateManagerTest {
private PartitionService partitionServiceMock;
@Captor
private static ArgumentCaptor<TbQueueCallback> queueCallbackCaptor;
private ArgumentCaptor<TbQueueCallback> queueCallbackCaptor;
private static DefaultRuleEngineDeviceStateManager deviceStateManager;
private DefaultRuleEngineDeviceStateManager deviceStateManager;
private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("57ab2e6c-bc4c-11ee-a506-0242ac120002"));
private static final DeviceId DEVICE_ID = DeviceId.fromString("74a9053e-bc4c-11ee-a506-0242ac120002");
@ -87,15 +89,17 @@ public class DefaultRuleEngineDeviceStateManagerTest {
@DisplayName("Given event should be routed to local service and event processed has succeeded, " +
"when onDeviceX() is called, then should route event to local service and call onSuccess() callback.")
@MethodSource
public void givenRoutedToLocalAndProcessingSuccess_whenOnDeviceAction_thenShouldCallLocalServiceAndSuccessCallback(Runnable onDeviceAction, Runnable actionVerification) {
public void givenRoutedToLocalAndProcessingSuccess_whenOnDeviceAction_thenShouldCallLocalServiceAndSuccessCallback(
BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback> onDeviceAction, Consumer<DeviceStateService> actionVerification
) {
// GIVEN
given(serviceInfoProviderMock.isService(ServiceType.TB_CORE)).willReturn(true);
given(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).willReturn(MY_TPI);
onDeviceAction.run();
onDeviceAction.accept(deviceStateManager, tbCallbackMock);
// THEN
actionVerification.run();
actionVerification.accept(deviceStateServiceMock);
then(clusterServiceMock).shouldHaveNoInteractions();
then(tbCallbackMock).should().onSuccess();
@ -105,20 +109,20 @@ public class DefaultRuleEngineDeviceStateManagerTest {
private static Stream<Arguments> givenRoutedToLocalAndProcessingSuccess_whenOnDeviceAction_thenShouldCallLocalServiceAndSuccessCallback() {
return Stream.of(
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS)
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS)
),
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS)
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS)
),
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS)
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS)
),
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS)
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS)
)
);
}
@ -128,19 +132,19 @@ public class DefaultRuleEngineDeviceStateManagerTest {
"when onDeviceX() is called, then should route event to local service and call onFailure() callback.")
@MethodSource
public void givenRoutedToLocalAndProcessingFailure_whenOnDeviceAction_thenShouldCallLocalServiceAndFailureCallback(
Runnable exceptionThrowSetup, Runnable onDeviceAction, Runnable actionVerification
Consumer<DeviceStateService> exceptionThrowSetup, BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback> onDeviceAction, Consumer<DeviceStateService> actionVerification
) {
// GIVEN
given(serviceInfoProviderMock.isService(ServiceType.TB_CORE)).willReturn(true);
given(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).willReturn(MY_TPI);
exceptionThrowSetup.run();
exceptionThrowSetup.accept(deviceStateServiceMock);
// WHEN
onDeviceAction.run();
onDeviceAction.accept(deviceStateManager, tbCallbackMock);
// THEN
actionVerification.run();
actionVerification.accept(deviceStateServiceMock);
then(clusterServiceMock).shouldHaveNoInteractions();
then(tbCallbackMock).should(never()).onSuccess();
@ -150,24 +154,24 @@ public class DefaultRuleEngineDeviceStateManagerTest {
private static Stream<Arguments> givenRoutedToLocalAndProcessingFailure_whenOnDeviceAction_thenShouldCallLocalServiceAndFailureCallback() {
return Stream.of(
Arguments.of(
(Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS),
(Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS)
(Consumer<DeviceStateService>) deviceStateServiceMock -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS),
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS)
),
Arguments.of(
(Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS),
(Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS)
(Consumer<DeviceStateService>) deviceStateServiceMock -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS),
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS)
),
Arguments.of(
(Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS),
(Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS)
(Consumer<DeviceStateService>) deviceStateServiceMock -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS),
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS)
),
Arguments.of(
(Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS),
(Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS)
(Consumer<DeviceStateService>) deviceStateServiceMock -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS),
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Consumer<DeviceStateService>) deviceStateServiceMock -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS)
)
);
}
@ -176,16 +180,18 @@ public class DefaultRuleEngineDeviceStateManagerTest {
@DisplayName("Given event should be routed to external service, " +
"when onDeviceX() is called, then should send correct queue message to external service with correct callback object.")
@MethodSource
public void givenRoutedToExternal_whenOnDeviceAction_thenShouldSendQueueMsgToExternalServiceWithCorrectCallback(Runnable onDeviceAction, Runnable actionVerification) {
public void givenRoutedToExternal_whenOnDeviceAction_thenShouldSendQueueMsgToExternalServiceWithCorrectCallback(
BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback> onDeviceAction, BiConsumer<TbClusterService, ArgumentCaptor<TbQueueCallback>> actionVerification
) {
// WHEN
ReflectionTestUtils.setField(deviceStateManager, "deviceStateService", Optional.empty());
given(serviceInfoProviderMock.isService(ServiceType.TB_CORE)).willReturn(false);
given(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).willReturn(EXTERNAL_TPI);
onDeviceAction.run();
onDeviceAction.accept(deviceStateManager, tbCallbackMock);
// THEN
actionVerification.run();
actionVerification.accept(clusterServiceMock, queueCallbackCaptor);
TbQueueCallback callback = queueCallbackCaptor.getValue();
callback.onSuccess(metadataMock);
@ -197,8 +203,8 @@ public class DefaultRuleEngineDeviceStateManagerTest {
private static Stream<Arguments> givenRoutedToExternal_whenOnDeviceAction_thenShouldSendQueueMsgToExternalServiceWithCorrectCallback() {
return Stream.of(
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> {
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(BiConsumer<TbClusterService, ArgumentCaptor<TbQueueCallback>>) (clusterServiceMock, queueCallbackCaptor) -> {
var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder()
.setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits())
.setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits())
@ -213,8 +219,8 @@ public class DefaultRuleEngineDeviceStateManagerTest {
}
),
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> {
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(BiConsumer<TbClusterService, ArgumentCaptor<TbQueueCallback>>) (clusterServiceMock, queueCallbackCaptor) -> {
var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder()
.setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits())
.setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits())
@ -229,8 +235,8 @@ public class DefaultRuleEngineDeviceStateManagerTest {
}
),
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> {
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(BiConsumer<TbClusterService, ArgumentCaptor<TbQueueCallback>>) (clusterServiceMock, queueCallbackCaptor) -> {
var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder()
.setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits())
.setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits())
@ -245,8 +251,8 @@ public class DefaultRuleEngineDeviceStateManagerTest {
}
),
Arguments.of(
(Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(Runnable) () -> {
(BiConsumer<DefaultRuleEngineDeviceStateManager, TbCallback>) (deviceStateManager, tbCallbackMock) -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock),
(BiConsumer<TbClusterService, ArgumentCaptor<TbQueueCallback>>) (clusterServiceMock, queueCallbackCaptor) -> {
var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder()
.setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits())
.setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits())

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

Loading…
Cancel
Save