318 changed files with 8632 additions and 2499 deletions
File diff suppressed because one or more lines are too long
@ -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" |
||||
|
] |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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"); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import lombok.AccessLevel; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Data |
||||
|
@ToString(callSuper = true) |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED) |
||||
|
public class AlarmsDeletionHousekeeperTask extends HousekeeperTask { |
||||
|
|
||||
|
private List<UUID> alarms; |
||||
|
|
||||
|
public AlarmsDeletionHousekeeperTask(TenantId tenantId, EntityId entityId) { |
||||
|
this(tenantId, entityId, null); |
||||
|
} |
||||
|
|
||||
|
public AlarmsDeletionHousekeeperTask(TenantId tenantId, EntityId entityId, List<UUID> alarms) { |
||||
|
super(tenantId, entityId, HousekeeperTaskType.DELETE_ALARMS); |
||||
|
this.alarms = alarms; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getDescription() { |
||||
|
return super.getDescription() + (alarms != null ? " (" + alarms + ")" : ""); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import lombok.AccessLevel; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
|
||||
|
@Data |
||||
|
@ToString(callSuper = true) |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED) |
||||
|
public class AlarmsUnassignHousekeeperTask extends HousekeeperTask { |
||||
|
|
||||
|
private String userTitle; |
||||
|
|
||||
|
protected AlarmsUnassignHousekeeperTask(User user) { |
||||
|
super(user.getTenantId(), user.getId(), HousekeeperTaskType.UNASSIGN_ALARMS); |
||||
|
this.userTitle = user.getTitle(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Data |
||||
|
@ToString(callSuper = true) |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@NoArgsConstructor |
||||
|
public class EntitiesDeletionHousekeeperTask extends HousekeeperTask { |
||||
|
|
||||
|
private EntityType entityType; |
||||
|
private List<UUID> entities; |
||||
|
|
||||
|
public EntitiesDeletionHousekeeperTask(TenantId tenantId, EntityType entityType, List<UUID> entities) { |
||||
|
super(tenantId, tenantId, HousekeeperTaskType.DELETE_ENTITIES); |
||||
|
this.entityType = entityType; |
||||
|
this.entities = entities; |
||||
|
} |
||||
|
|
||||
|
@JsonIgnore |
||||
|
@Override |
||||
|
public String getDescription() { |
||||
|
return entityType.getNormalName().toLowerCase() + "s deletion (" + entities + ")"; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes.Type; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
import lombok.AccessLevel; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.NonNull; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
@JsonIgnoreProperties(ignoreUnknown = true) |
||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "taskType", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = HousekeeperTask.class) |
||||
|
@JsonSubTypes({ |
||||
|
@Type(name = "DELETE_TS_HISTORY", value = TsHistoryDeletionHousekeeperTask.class), |
||||
|
@Type(name = "DELETE_LATEST_TS", value = LatestTsDeletionHousekeeperTask.class), |
||||
|
@Type(name = "DELETE_TENANT_ENTITIES", value = TenantEntitiesDeletionHousekeeperTask.class), |
||||
|
@Type(name = "DELETE_ENTITIES", value = EntitiesDeletionHousekeeperTask.class), |
||||
|
@Type(name = "DELETE_ALARMS", value = AlarmsDeletionHousekeeperTask.class), |
||||
|
@Type(name = "UNASSIGN_ALARMS", value = AlarmsUnassignHousekeeperTask.class) |
||||
|
}) |
||||
|
@Data |
||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED) |
||||
|
public class HousekeeperTask implements Serializable { |
||||
|
|
||||
|
private TenantId tenantId; |
||||
|
private EntityId entityId; |
||||
|
private HousekeeperTaskType taskType; |
||||
|
private long ts; |
||||
|
|
||||
|
protected HousekeeperTask(@NonNull TenantId tenantId, @NonNull EntityId entityId, @NonNull HousekeeperTaskType taskType) { |
||||
|
this.tenantId = tenantId; |
||||
|
this.entityId = entityId; |
||||
|
this.taskType = taskType; |
||||
|
this.ts = System.currentTimeMillis(); |
||||
|
} |
||||
|
|
||||
|
public static HousekeeperTask deleteAttributes(TenantId tenantId, EntityId entityId) { |
||||
|
return new HousekeeperTask(tenantId, entityId, HousekeeperTaskType.DELETE_ATTRIBUTES); |
||||
|
} |
||||
|
|
||||
|
public static HousekeeperTask deleteTelemetry(TenantId tenantId, EntityId entityId) { |
||||
|
return new HousekeeperTask(tenantId, entityId, HousekeeperTaskType.DELETE_TELEMETRY); |
||||
|
} |
||||
|
|
||||
|
public static HousekeeperTask deleteEvents(TenantId tenantId, EntityId entityId) { |
||||
|
return new HousekeeperTask(tenantId, entityId, HousekeeperTaskType.DELETE_EVENTS); |
||||
|
} |
||||
|
|
||||
|
public static HousekeeperTask unassignAlarms(User user) { |
||||
|
return new AlarmsUnassignHousekeeperTask(user); |
||||
|
} |
||||
|
|
||||
|
public static HousekeeperTask deleteAlarms(TenantId tenantId, EntityId entityId) { |
||||
|
return new AlarmsDeletionHousekeeperTask(tenantId, entityId); |
||||
|
} |
||||
|
|
||||
|
public static HousekeeperTask deleteTenantEntities(TenantId tenantId, EntityType entityType) { |
||||
|
return new TenantEntitiesDeletionHousekeeperTask(tenantId, entityType); |
||||
|
} |
||||
|
|
||||
|
@JsonIgnore |
||||
|
public String getDescription() { |
||||
|
return taskType.getDescription() + " for " + entityId.getEntityType().getNormalName().toLowerCase() + " " + entityId.getId(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
|
||||
|
@RequiredArgsConstructor |
||||
|
@Getter |
||||
|
public enum HousekeeperTaskType { |
||||
|
|
||||
|
DELETE_ATTRIBUTES("attributes deletion"), |
||||
|
DELETE_TELEMETRY("telemetry deletion"), |
||||
|
DELETE_LATEST_TS("latest telemetry deletion"), |
||||
|
DELETE_TS_HISTORY("timeseries history deletion"), |
||||
|
DELETE_EVENTS("events deletion"), |
||||
|
DELETE_ALARMS("alarms deletion"), |
||||
|
UNASSIGN_ALARMS("alarms unassigning"), |
||||
|
DELETE_TENANT_ENTITIES("tenant entities deletion"), |
||||
|
DELETE_ENTITIES("entities deletion"); |
||||
|
|
||||
|
private final String description; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import lombok.AccessLevel; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
@Data |
||||
|
@ToString(callSuper = true) |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED) |
||||
|
public class LatestTsDeletionHousekeeperTask extends HousekeeperTask { |
||||
|
|
||||
|
private String key; |
||||
|
|
||||
|
public LatestTsDeletionHousekeeperTask(TenantId tenantId, EntityId entityId, String key) { |
||||
|
super(tenantId, entityId, HousekeeperTaskType.DELETE_LATEST_TS); |
||||
|
this.key = key; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getDescription() { |
||||
|
return super.getDescription() + (key != null ? " for key '" + key + "'" : ""); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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.common.data.housekeeper; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
@Data |
||||
|
@ToString(callSuper = true) |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@NoArgsConstructor |
||||
|
public class TenantEntitiesDeletionHousekeeperTask extends HousekeeperTask { |
||||
|
|
||||
|
private EntityType entityType; |
||||
|
|
||||
|
public TenantEntitiesDeletionHousekeeperTask(TenantId tenantId, EntityType entityType) { |
||||
|
super(tenantId, tenantId, HousekeeperTaskType.DELETE_TENANT_ENTITIES); |
||||
|
this.entityType = entityType; |
||||
|
} |
||||
|
|
||||
|
@JsonIgnore |
||||
|
@Override |
||||
|
public String getDescription() { |
||||
|
return entityType.getNormalName().toLowerCase() + "s deletion"; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.housekeeper; |
||||
|
|
||||
|
import lombok.AccessLevel; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
@Data |
||||
|
@ToString(callSuper = true) |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED) |
||||
|
public class TsHistoryDeletionHousekeeperTask extends HousekeeperTask { |
||||
|
|
||||
|
private String key; |
||||
|
|
||||
|
public TsHistoryDeletionHousekeeperTask(TenantId tenantId, EntityId entityId, String key) { |
||||
|
super(tenantId, entityId, HousekeeperTaskType.DELETE_TS_HISTORY); |
||||
|
this.key = key; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getDescription() { |
||||
|
return super.getDescription() + (key != null ? " for key '" + key + "'" : ""); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.notification.info; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
@Builder |
||||
|
public class TaskProcessingFailureNotificationInfo implements RuleOriginatedNotificationInfo { |
||||
|
|
||||
|
private TenantId tenantId; |
||||
|
private EntityId entityId; |
||||
|
private HousekeeperTaskType taskType; |
||||
|
private String taskDescription; |
||||
|
private String error; |
||||
|
private int attempt; |
||||
|
|
||||
|
@Override |
||||
|
public Map<String, String> getTemplateData() { |
||||
|
return mapOf( |
||||
|
"tenantId", tenantId.toString(), |
||||
|
"entityType", entityId.getEntityType().getNormalName(), |
||||
|
"entityId", entityId.getId().toString(), |
||||
|
"taskType", taskType.getDescription(), |
||||
|
"taskDescription", taskDescription, |
||||
|
"error", error, |
||||
|
"attempt", String.valueOf(attempt) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TenantId getAffectedTenantId() { |
||||
|
return tenantId; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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.common.data.notification.rule.trigger; |
||||
|
|
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; |
||||
|
|
||||
|
@Data |
||||
|
@Builder |
||||
|
public class TaskProcessingFailureTrigger implements NotificationRuleTrigger { |
||||
|
|
||||
|
private final HousekeeperTask task; |
||||
|
private final int attempt; |
||||
|
private final Throwable error; |
||||
|
|
||||
|
@Override |
||||
|
public NotificationRuleTriggerType getType() { |
||||
|
return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TenantId getTenantId() { |
||||
|
return task.getTenantId(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityId getOriginatorEntityId() { |
||||
|
return task.getEntityId(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean deduplicate() { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue