From 89e7b95d1965cbb2ce251c5c5640d0327ee1856e Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 6 Feb 2025 17:53:46 +0200 Subject: [PATCH 01/14] Resources shortage notification template & rule --- .../install/ThingsboardInstallService.java | 1 + ...esourcesShortageLimitTriggerProcessor.java | 50 +++++++++++++ .../system/DefaultSystemInfoService.java | 22 ++++-- .../data/notification/NotificationType.java | 3 +- .../ResourcesShortageNotificationInfo.java | 42 +++++++++++ .../trigger/NewPlatformVersionTrigger.java | 5 -- .../trigger/ResourcesShortageTrigger.java | 71 +++++++++++++++++++ .../trigger/TaskProcessingFailureTrigger.java | 10 +-- .../config/NotificationRuleTriggerConfig.java | 3 +- .../config/NotificationRuleTriggerType.java | 3 +- ...ShortageNotificationRuleTriggerConfig.java | 48 +++++++++++++ .../DefaultNotificationSettingsService.java | 4 ++ .../notification/DefaultNotifications.java | 16 +++++ .../rule-notification-dialog.component.html | 60 ++++++++++++++++ .../rule-notification-dialog.component.ts | 15 +++- .../template-notification-dialog.component.ts | 3 +- .../app/shared/models/notification.models.ts | 16 ++++- .../en_US/notification/resources_shortage.md | 42 +++++++++++ .../assets/locale/locale.constant-en_US.json | 8 ++- 19 files changed, 398 insertions(+), 24 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java create mode 100644 ui-ngx/src/assets/help/en_US/notification/resources_shortage.md diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index ea4c059222..69df861f3a 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -116,6 +116,7 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createDatabaseIndexes(); // TODO: cleanup update code after each release + systemDataLoaderService.updateDefaultNotificationConfigs(false); // Runs upgrade scripts that are not possible in plain SQL. dataUpdateService.updateData(); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java new file mode 100644 index 0000000000..c9c9a03f55 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java @@ -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.service.notification.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.notification.info.ResourcesShortageNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.ResourcesShortageTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.notification.rule.trigger.config.ResourcesShortageNotificationRuleTriggerConfig; + +@Service +@RequiredArgsConstructor +public class ResourcesShortageLimitTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(ResourcesShortageTrigger trigger, ResourcesShortageNotificationRuleTriggerConfig triggerConfig) { + float usagePercent = trigger.getUsage() / 100.0f; + return switch (trigger.getResource()) { + case CPU -> usagePercent >= triggerConfig.getCpuThreshold(); + case RAM -> usagePercent >= triggerConfig.getRamThreshold(); + case STORAGE -> usagePercent >= triggerConfig.getStorageThreshold(); + }; + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(ResourcesShortageTrigger trigger) { + return ResourcesShortageNotificationInfo.builder().resource(trigger.getResource().name()).usage(trigger.getUsage()).build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RESOURCES_SHORTAGE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index 42d468575a..53d94af8e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -39,6 +39,9 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.notification.rule.trigger.ResourcesShortageTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.ResourcesShortageTrigger.Resource; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.stats.TbApiUsageStateClient; import org.thingsboard.server.dao.domain.DomainService; @@ -92,6 +95,7 @@ public class DefaultSystemInfoService extends TbApplicationEventListener 0; + return !providers.isEmpty(); } } return false; @@ -188,9 +192,18 @@ public class DefaultSystemInfoService extends TbApplicationEventListener tsList = new ArrayList<>(); tsList.add(new BasicTsKvEntry(ts, new BooleanDataEntry("clusterMode", false))); - getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v)))); - getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v)))); - getDiscSpaceUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v)))); + getCpuUsage().ifPresent(v -> { + tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v))); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(v).build()); + }); + getMemoryUsage().ifPresent(v -> { + tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v))); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(v).build()); + }); + getDiscSpaceUsage().ifPresent(v -> { + tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v))); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(v).build()); + }); getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v)))); getTotalMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalMemory", v)))); @@ -244,4 +257,5 @@ public class DefaultSystemInfoService extends TbApplicationEventListener getTemplateData() { + return Map.of( + "resource", resource, + "usage", String.valueOf(usage) + ); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionTrigger.java index 84b431dd87..4f89cabd18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionTrigger.java @@ -55,9 +55,4 @@ public class NewPlatformVersionTrigger implements NotificationRuleTrigger { updateInfo.getCurrentVersion(), updateInfo.getLatestVersion()); } - @Override - public long getDefaultDeduplicationDuration() { - return 0; - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java new file mode 100644 index 0000000000..b35d0b8470 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java @@ -0,0 +1,71 @@ +/** + * 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.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; + +import java.io.Serial; +import java.util.concurrent.TimeUnit; + +@Data +@Builder +public class ResourcesShortageTrigger implements NotificationRuleTrigger { + + @Serial + private static final long serialVersionUID = 6024216015202949570L; + + private Resource resource; + private Integer usage; + + @Override + public TenantId getTenantId() { + return TenantId.SYS_TENANT_ID; + } + + @Override + public EntityId getOriginatorEntityId() { + return TenantId.SYS_TENANT_ID; + } + + @Override + public boolean deduplicate() { + return true; + } + + @Override + public String getDeduplicationKey() { + return resource.name(); + } + + @Override + public long getDefaultDeduplicationDuration() { + return TimeUnit.HOURS.toMillis(1); + } + + @Override + public NotificationRuleTriggerType getType() { + return NotificationRuleTriggerType.RESOURCES_SHORTAGE; + } + + public enum Resource { + CPU, RAM, STORAGE + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java index 3626e09c0b..f531aabc46 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java @@ -22,10 +22,15 @@ 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; +import java.io.Serial; + @Data @Builder public class TaskProcessingFailureTrigger implements NotificationRuleTrigger { + @Serial + private static final long serialVersionUID = 5606203770553105345L; + private final HousekeeperTask task; private final int attempt; private final Throwable error; @@ -45,9 +50,4 @@ public class TaskProcessingFailureTrigger implements NotificationRuleTrigger { return task.getEntityId(); } - @Override - public boolean deduplicate() { - return false; - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java index 1621ec8120..c8a69e559f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java @@ -38,7 +38,8 @@ import java.io.Serializable; @Type(value = RateLimitsNotificationRuleTriggerConfig.class, name = "RATE_LIMITS"), @Type(value = EdgeConnectionNotificationRuleTriggerConfig.class, name = "EDGE_CONNECTION"), @Type(value = EdgeCommunicationFailureNotificationRuleTriggerConfig.class, name = "EDGE_COMMUNICATION_FAILURE"), - @Type(value = TaskProcessingFailureNotificationRuleTriggerConfig.class, name = "TASK_PROCESSING_FAILURE") + @Type(value = TaskProcessingFailureNotificationRuleTriggerConfig.class, name = "TASK_PROCESSING_FAILURE"), + @Type(value = ResourcesShortageNotificationRuleTriggerConfig.class, name = "RESOURCES_SHORTAGE") }) public interface NotificationRuleTriggerConfig extends Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java index 8846ae284d..3be4f4da73 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java @@ -32,7 +32,8 @@ public enum NotificationRuleTriggerType { ENTITIES_LIMIT(false), API_USAGE_LIMIT(false), RATE_LIMITS(false), - TASK_PROCESSING_FAILURE(false); + TASK_PROCESSING_FAILURE(false), + RESOURCES_SHORTAGE(false); private final boolean tenantLevel; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..17de9e9246 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.rule.trigger.config; + +import jakarta.validation.constraints.Max; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ResourcesShortageNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + + @Serial + private static final long serialVersionUID = 339395299693241424L; + + private String resource; + @Max(1) + private float cpuThreshold; // in percents + @Max(1) + private float ramThreshold; // in percents + @Max(1) + private float storageThreshold; // in percents + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RESOURCES_SHORTAGE; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 1404eaff67..4ab22ea58d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -186,6 +186,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.newPlatformVersion, sysAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.taskProcessingFailure, tenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.resourcesShortage, sysAdmins.getId()); return; } @@ -221,6 +222,9 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS if (!isNotificationConfigured(tenantId, NotificationType.TASK_PROCESSING_FAILURE)) { defaultNotifications.create(tenantId, DefaultNotifications.taskProcessingFailure, sysAdmins.getId()); } + if (!isNotificationConfigured(tenantId, NotificationType.RESOURCES_SHORTAGE)) { + defaultNotifications.create(tenantId, DefaultNotifications.resourcesShortage, sysAdmins.getId()); + } } else { var requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTION, NotificationType.EDGE_COMMUNICATION_FAILURE); var existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 16088e83c9..f830665ba6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.notification.rule.DefaultNotificationR import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig; import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.ResourcesShortageTrigger.Resource; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmAssignmentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig; @@ -49,6 +50,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.NewPl import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.config.RateLimitsNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.ResourcesShortageNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.TaskProcessingFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -391,6 +393,20 @@ public class DefaultNotifications { .color(RED_COLOR) .build(); + public static final DefaultNotification resourcesShortage = DefaultNotification.builder() + .name("Resources shortage notification") + .type(NotificationType.RESOURCES_SHORTAGE) + .subject("Warning: ${resource} shortage") + .text("The ${resource} usage is on the rise (currently at ${usage}%). Immediate action required to prevent system instability.") + .icon("warning") + .rule(DefaultRule.builder() + .name("Resources shortage") + .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().resource(Resource.CPU.name()).cpuThreshold(90).storageThreshold(90).ramThreshold(90).build()) + .description("Send notification to system admins when resources shortage is running low") + .build()) + .color(RED_COLOR) + .build(); + private final NotificationTemplateService templateService; private final NotificationRuleService ruleService; diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html index 2d7ef41bd5..23a7830bab 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -591,6 +591,66 @@ + + + {{ 'notification.resources-shortage-trigger-settings' | translate }} +
+
+ notification.filter +
+ +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+
+
+
+
+ + notification.description + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index bc6935b4e7..7c43dbf9fb 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -102,6 +102,7 @@ export class RuleNotificationDialogComponent extends edgeCommunicationFailureTemplateForm: FormGroup; edgeConnectionTemplateForm: FormGroup; taskProcessingFailureTemplateForm: FormGroup; + resourceUsageShortageTemplateForm: FormGroup; triggerType = TriggerType; triggerTypes: TriggerType[]; @@ -344,6 +345,14 @@ export class RuleNotificationDialogComponent extends }) }); + this.resourceUsageShortageTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + cpuThreshold: [.9, [Validators.min(0), Validators.max(1)]], + ramThreshold: [.9, [Validators.min(0), Validators.max(1)]], + storageThreshold: [.9, [Validators.min(0), Validators.max(1)]] + }) + }); + this.triggerTypeFormsMap = new Map([ [TriggerType.ALARM, this.alarmTemplateForm], [TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm], @@ -357,7 +366,8 @@ export class RuleNotificationDialogComponent extends [TriggerType.RATE_LIMITS, this.rateLimitsTemplateForm], [TriggerType.EDGE_COMMUNICATION_FAILURE, this.edgeCommunicationFailureTemplateForm], [TriggerType.EDGE_CONNECTION, this.edgeConnectionTemplateForm], - [TriggerType.TASK_PROCESSING_FAILURE, this.taskProcessingFailureTemplateForm] + [TriggerType.TASK_PROCESSING_FAILURE, this.taskProcessingFailureTemplateForm], + [TriggerType.RESOURCES_SHORTAGE, this.resourceUsageShortageTemplateForm] ]); if (data.isAdd || data.isCopy) { @@ -497,7 +507,8 @@ export class RuleNotificationDialogComponent extends TriggerType.API_USAGE_LIMIT, TriggerType.NEW_PLATFORM_VERSION, TriggerType.RATE_LIMITS, - TriggerType.TASK_PROCESSING_FAILURE + TriggerType.TASK_PROCESSING_FAILURE, + TriggerType.RESOURCES_SHORTAGE ]); if (this.isSysAdmin()) { diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts index ccbcb6bf22..3495d43338 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts @@ -180,7 +180,8 @@ export class TemplateNotificationDialogComponent NotificationType.API_USAGE_LIMIT, NotificationType.NEW_PLATFORM_VERSION, NotificationType.RATE_LIMITS, - NotificationType.TASK_PROCESSING_FAILURE + NotificationType.TASK_PROCESSING_FAILURE, + NotificationType.RESOURCES_SHORTAGE ]); if (this.isSysAdmin()) { diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 365adda774..088ae2555c 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -526,7 +526,8 @@ export enum NotificationType { RATE_LIMITS = 'RATE_LIMITS', EDGE_CONNECTION = 'EDGE_CONNECTION', EDGE_COMMUNICATION_FAILURE = 'EDGE_COMMUNICATION_FAILURE', - TASK_PROCESSING_FAILURE = 'TASK_PROCESSING_FAILURE' + TASK_PROCESSING_FAILURE = 'TASK_PROCESSING_FAILURE', + RESOURCES_SHORTAGE = 'RESOURCES_SHORTAGE' } export const NotificationTypeIcons = new Map([ @@ -538,7 +539,8 @@ export const NotificationTypeIcons = new Map([ [NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'settings_ethernet'], [NotificationType.ENTITIES_LIMIT, 'data_thresholding'], [NotificationType.API_USAGE_LIMIT, 'insert_chart'], - [NotificationType.TASK_PROCESSING_FAILURE, 'warning'] + [NotificationType.TASK_PROCESSING_FAILURE, 'warning'], + [NotificationType.RESOURCES_SHORTAGE, 'warning'] ]); export const AlarmSeverityNotificationColors = new Map( @@ -657,6 +659,12 @@ export const NotificationTemplateTypeTranslateMap = new Map([ @@ -688,7 +697,8 @@ export const TriggerTypeTranslationMap = new Map([ [TriggerType.RATE_LIMITS, 'notification.trigger.rate-limits'], [TriggerType.EDGE_CONNECTION, 'notification.trigger.edge-connection'], [TriggerType.EDGE_COMMUNICATION_FAILURE, 'notification.trigger.edge-communication-failure'], - [TriggerType.TASK_PROCESSING_FAILURE, 'notification.trigger.task-processing-failure'] + [TriggerType.TASK_PROCESSING_FAILURE, 'notification.trigger.task-processing-failure'], + [TriggerType.RESOURCES_SHORTAGE, 'notification.trigger.resources-shortage'] ]); export interface NotificationUserSettings { diff --git a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md new file mode 100644 index 0000000000..eb4ef57f7a --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md @@ -0,0 +1,42 @@ +#### Resource usage shortage notification templatization + +
+
+ +Notification subject and message fields support templatization. +The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + +* `cpuThreshold` - the CPU shortage threshold; +* `ramThreshold` - the RAM shortage threshold; +* `storageThreshold` - the Storage shortage threshold; + +Parameter names must be wrapped using `${...}`. For example: `${entityType}`. +You may also modify the value of the parameter with one of the suffixes: + +* `upperCase`, for example - `${entityType:upperCase}` +* `lowerCase`, for example - `${entityType:lowerCase}` +* `capitalize`, for example - `${entityType:capitalize}` + +
+ +##### Examples + +Let's assume there is a resource usage shortage and the system is low on free resources (CPU, RAM, or Storage). +The following template: + +```text +Warning: ${resource} is critically high at ${usage}% +{:copy-code} +``` + +will be transformed to: + +```text +Warning: CPU is critically high at 83% +``` + +
+
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 57df3865e9..124826fc9c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3728,6 +3728,7 @@ "new-platform-version-trigger-settings": "New platform version trigger settings", "rate-limits-trigger-settings": "Exceeded rate limits trigger settings", "task-processing-failure-trigger-settings": "Task processing failure trigger settings", + "resources-shortage-trigger-settings": "Resources shortage trigger settings", "at-least-one-should-be-selected": "At least one should be selected", "basic-settings": "Basic settings", "button-text": "Button text", @@ -3742,6 +3743,7 @@ "create-new": "Create new", "created": "Created", "customize-messages": "Customize messages", + "cpu-threshold": "CPU threshold", "delete-notification-text": "Be careful, after the confirmation the notification will become unrecoverable.", "delete-notification-title": "Are you sure you want to delete the notification?", "delete-notifications-text": "Be careful, after the confirmation notifications will become unrecoverable.", @@ -3857,6 +3859,7 @@ "only-rule-chain-lifecycle-failures": "Only rule chain lifecycle failures", "only-rule-node-lifecycle-failures": "Only rule node lifecycle failures", "platform-users": "Platform users", + "ram-threshold": "RAM threshold", "rate-limits": "Rate limits", "rate-limits-hint": "If the field is empty, the trigger will be applied to all rate limits", "recipient": "Recipient", @@ -3922,6 +3925,7 @@ "start-from-scratch": "Start from scratch", "status": "Status", "stop-escalation-alarm-status-become": "Stop the escalation on the alarm status become:", + "storage-threshold": "Storage threshold", "subject": "Subject", "subject-required": "Subject is required", "subject-max-length": "Subject should be less than or equal to {{ length }} characters", @@ -3943,7 +3947,8 @@ "rate-limits": "Exceeded rate limits", "edge-communication-failure": "Edge communication failure", "edge-connection": "Edge connection", - "task-processing-failure": "Task processing failure" + "task-processing-failure": "Task processing failure", + "resources-shortage": "Resources shortage" }, "templates": "Templates", "notification-templates": "Notifications / Templates", @@ -3967,6 +3972,7 @@ "edge-connection": "Edge connection", "edge-communication-failure": "Edge communication failure", "task-processing-failure": "Task processing failure", + "resources-shortage": "Resources shortage", "trigger": "Trigger", "trigger-required": "Trigger is required" }, From bad6451102396a2ab1431324deee60a838ad1676 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Feb 2025 14:30:42 +0200 Subject: [PATCH 02/14] Improve after review --- ...rocessor.java => ResourcesShortageTriggerProcessor.java} | 2 +- .../server/dao/notification/DefaultNotifications.java | 4 ++-- .../notification/rule/rule-notification-dialog.component.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/{ResourcesShortageLimitTriggerProcessor.java => ResourcesShortageTriggerProcessor.java} (92%) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java index c9c9a03f55..b7da85e3e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageLimitTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Resou @Service @RequiredArgsConstructor -public class ResourcesShortageLimitTriggerProcessor implements NotificationRuleTriggerProcessor { +public class ResourcesShortageTriggerProcessor implements NotificationRuleTriggerProcessor { @Override public boolean matchesFilter(ResourcesShortageTrigger trigger, ResourcesShortageNotificationRuleTriggerConfig triggerConfig) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index f830665ba6..2716a6492b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -397,11 +397,11 @@ public class DefaultNotifications { .name("Resources shortage notification") .type(NotificationType.RESOURCES_SHORTAGE) .subject("Warning: ${resource} shortage") - .text("The ${resource} usage is on the rise (currently at ${usage}%). Immediate action required to prevent system instability.") + .text("${resource} usage is at ${usage}%).") .icon("warning") .rule(DefaultRule.builder() .name("Resources shortage") - .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().resource(Resource.CPU.name()).cpuThreshold(90).storageThreshold(90).ramThreshold(90).build()) + .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().resource(Resource.CPU.name()).cpuThreshold(80).storageThreshold(80).ramThreshold(80).build()) .description("Send notification to system admins when resources shortage is running low") .build()) .color(RED_COLOR) diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index 7c43dbf9fb..d49cf25d29 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -347,9 +347,9 @@ export class RuleNotificationDialogComponent extends this.resourceUsageShortageTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ - cpuThreshold: [.9, [Validators.min(0), Validators.max(1)]], - ramThreshold: [.9, [Validators.min(0), Validators.max(1)]], - storageThreshold: [.9, [Validators.min(0), Validators.max(1)]] + cpuThreshold: [.8, [Validators.min(0), Validators.max(1)]], + ramThreshold: [.8, [Validators.min(0), Validators.max(1)]], + storageThreshold: [.8, [Validators.min(0), Validators.max(1)]] }) }); From e14c81ece33c7fcb2f58a7688b0a388a89fcae90 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 24 Feb 2025 16:38:08 +0200 Subject: [PATCH 03/14] Fix default resourcesShortage --- .../server/dao/notification/DefaultNotifications.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 2716a6492b..a1944f78ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -401,7 +401,7 @@ public class DefaultNotifications { .icon("warning") .rule(DefaultRule.builder() .name("Resources shortage") - .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().resource(Resource.CPU.name()).cpuThreshold(80).storageThreshold(80).ramThreshold(80).build()) + .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().resource(Resource.CPU.name()).cpuThreshold(0.8f).storageThreshold(0.8f).ramThreshold(0.8f).build()) .description("Send notification to system admins when resources shortage is running low") .build()) .color(RED_COLOR) From a4948891b2ad28940ea36ef3dbf94a21325573cd Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 26 Feb 2025 13:13:34 +0200 Subject: [PATCH 04/14] Make notification for microservices --- .../system/DefaultSystemInfoService.java | 17 ++++++++++++----- .../info/ResourcesShortageNotificationInfo.java | 2 +- .../rule/trigger/ResourcesShortageTrigger.java | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index 53d94af8e4..515da7703a 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -184,6 +184,11 @@ public class DefaultSystemInfoService extends TbApplicationEventListener clusterSystemData = getSystemData(serviceInfoProvider.getServiceInfo()); + clusterSystemData.forEach(data -> { + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(data.getCpuUsage()).build()); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(data.getMemoryUsage()).build()); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(data.getDiscUsage()).build()); + }); BasicTsKvEntry clusterDataKv = new BasicTsKvEntry(ts, new JsonDataEntry("clusterSystemData", JacksonUtil.toString(clusterSystemData))); doSave(Arrays.asList(new BasicTsKvEntry(ts, new BooleanDataEntry("clusterMode", true)), clusterDataKv)); } @@ -194,15 +199,17 @@ public class DefaultSystemInfoService extends TbApplicationEventListener { tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(v).build()); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage((long) v).build()); }); getMemoryUsage().ifPresent(v -> { - tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(v).build()); + long value = (long) v; + tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", value))); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(value).build()); }); getDiscSpaceUsage().ifPresent(v -> { - tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(v).build()); + long value = (long) v; + tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", value))); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(value).build()); }); getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v)))); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java index 1ebbe97f80..c052911b44 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java @@ -29,7 +29,7 @@ import java.util.Map; public class ResourcesShortageNotificationInfo implements RuleOriginatedNotificationInfo { private String resource; - private Integer usage; + private Long usage; @Override public Map getTemplateData() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java index b35d0b8470..0c4722647e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java @@ -32,7 +32,7 @@ public class ResourcesShortageTrigger implements NotificationRuleTrigger { private static final long serialVersionUID = 6024216015202949570L; private Resource resource; - private Integer usage; + private Long usage; @Override public TenantId getTenantId() { From f366e277df134ab8515ad936ba0cd4bbc3ef91ce Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 26 Feb 2025 15:13:24 +0200 Subject: [PATCH 05/14] Add tests for resource shortage --- .../system/DefaultSystemInfoService.java | 5 +- .../notification/NotificationRuleApiTest.java | 59 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index 515da7703a..f6d9ac158a 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -198,8 +198,9 @@ public class DefaultSystemInfoService extends TbApplicationEventListener tsList = new ArrayList<>(); tsList.add(new BasicTsKvEntry(ts, new BooleanDataEntry("clusterMode", false))); getCpuUsage().ifPresent(v -> { - tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage((long) v).build()); + long value = (long) v; + tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", value))); + notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(value).build()); }); getMemoryUsage().ifPresent(v -> { long value = (long) v; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 3cfb2dc42b..5fd9b48ea0 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -68,6 +68,8 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.ResourcesShortageTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.ResourcesShortageTrigger.Resource; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmAssignmentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig; @@ -78,6 +80,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Entit import org.thingsboard.server.common.data.notification.rule.trigger.config.NewPlatformVersionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.config.RateLimitsNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.ResourcesShortageNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.platform.AffectedTenantAdministratorsFilter; import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; @@ -98,8 +101,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.queue.notification.DefaultNotificationDeduplicationService; import org.thingsboard.server.service.notification.rule.cache.DefaultNotificationRulesCache; import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.system.DefaultSystemInfoService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -132,7 +137,9 @@ import static org.thingsboard.server.common.data.notification.rule.trigger.confi public class NotificationRuleApiTest extends AbstractNotificationApiTest { @SpyBean - private AlarmSubscriptionService alarmSubscriptionService; + private AlarmSubscriptionService alarmSubscriptionService;; + @Autowired + private DefaultSystemInfoService systemInfoService; @Autowired private NotificationRequestService notificationRequestService; @Autowired @@ -780,6 +787,56 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { }); } + @Test + public void testNotificationRuleProcessing_resourcesShortage() throws Exception { + loginSysAdmin(); + ResourcesShortageNotificationRuleTriggerConfig triggerConfig = ResourcesShortageNotificationRuleTriggerConfig.builder() + .resource(Resource.CPU.name()) + .cpuThreshold(0.01f) + .ramThreshold(0.01f) + .storageThreshold(0.01f) + .build(); + createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); + loginTenantAdmin(); + + Method method = DefaultSystemInfoService.class.getDeclaredMethod("saveCurrentMonolithSystemInfo"); + method.setAccessible(true); + method.invoke(systemInfoService); + + TimeUnit.SECONDS.sleep(5); + + assertThat(getMyNotifications(false, 100)).size().isEqualTo(3); + } + + @Test + public void testNotificationsDeduplication_resourcesShortage() throws Exception { + loginSysAdmin(); + ResourcesShortageNotificationRuleTriggerConfig triggerConfig = ResourcesShortageNotificationRuleTriggerConfig.builder() + .resource(Resource.CPU.name()) + .cpuThreshold(0.1f) + .build(); + createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); + loginTenantAdmin(); + + assertThat(getMyNotifications(false, 100)).size().isZero(); + for (int i = 0; i < 10; i++) { + notificationRuleProcessor.process(ResourcesShortageTrigger.builder() + .resource(Resource.CPU) + .usage(15L) + .build()); + TimeUnit.MILLISECONDS.sleep(300); + } + TimeUnit.SECONDS.sleep(5); + assertThat(getMyNotifications(false, 100)).size().isOne(); + + // deduplication is 5 minute, no new message is exp + notificationRuleProcessor.process(ResourcesShortageTrigger.builder() + .resource(Resource.CPU) + .usage(5L) + .build()); + await("").atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(getMyNotifications(false, 100)).size().isOne()); + } + @Test public void testNotificationRuleDisabling() throws Exception { EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); From 9f1578c72229230cbcdb9846e3dc581852d09f4c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 26 Feb 2025 15:52:16 +0200 Subject: [PATCH 06/14] Fix license header --- .../rule/trigger/ResourcesShortageTriggerProcessor.java | 2 +- .../notification/info/ResourcesShortageNotificationInfo.java | 2 +- .../notification/rule/trigger/ResourcesShortageTrigger.java | 2 +- .../config/ResourcesShortageNotificationRuleTriggerConfig.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java index b7da85e3e0..aefb628d2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ResourcesShortageTriggerProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2024 The Thingsboard Authors + * Copyright © 2016-2025 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java index c052911b44..24cb21febd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ResourcesShortageNotificationInfo.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2024 The Thingsboard Authors + * Copyright © 2016-2025 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java index 0c4722647e..f12c80d5db 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ResourcesShortageTrigger.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2024 The Thingsboard Authors + * Copyright © 2016-2025 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java index 17de9e9246..d542292113 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2024 The Thingsboard Authors + * Copyright © 2016-2025 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From a302451a5bd77f3edecffc575525e2648c2de496 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 26 Feb 2025 16:28:28 +0200 Subject: [PATCH 07/14] UI: Trigger settings to persent --- .../rule-notification-dialog.component.html | 32 +++++++++---------- .../rule-notification-dialog.component.scss | 2 +- .../rule-notification-dialog.component.ts | 27 ++++++++++++---- .../app/shared/models/notification.models.ts | 8 ++++- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html index 028796f470..0951acfea8 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -481,16 +481,16 @@ [allowedEntityTypes]="allowEntityTypeForEntitiesLimit">
- + notification.threshold
- + - + max="100"/>
@@ -599,44 +599,44 @@
notification.filter
- + notification.cpu-threshold
- + - + max="100"/>
- + notification.ram-threshold
- + - + max="100"/>
- + notification.storage-threshold
- + - + max="100"/>
diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss index 5e495655f1..9f1221cad5 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss @@ -81,7 +81,7 @@ min-width: 364px; } .limit-slider-container { - > label { + > span { margin-right: 16px; width: min-content; max-width: 40%; diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index 1ea55ed106..e0ddefcbe8 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -316,7 +316,7 @@ export class RuleNotificationDialogComponent extends this.entitiesLimitTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ entityTypes: [], - threshold: [.8, [Validators.min(0), Validators.max(1)]] + threshold: [80, [Validators.min(0), Validators.max(100)]] }) }); @@ -347,9 +347,9 @@ export class RuleNotificationDialogComponent extends this.resourceUsageShortageTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ - cpuThreshold: [.8, [Validators.min(0), Validators.max(1)]], - ramThreshold: [.8, [Validators.min(0), Validators.max(1)]], - storageThreshold: [.8, [Validators.min(0), Validators.max(1)]] + cpuThreshold: [80, [Validators.min(0), Validators.max(100)]], + ramThreshold: [80, [Validators.min(0), Validators.max(100)]], + storageThreshold: [80, [Validators.min(0), Validators.max(100)]] }) }); @@ -390,6 +390,14 @@ export class RuleNotificationDialogComponent extends this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice') .patchValue(!!this.ruleNotification.triggerConfig.devices, {onlySelf: true}); } + if (this.ruleNotification.triggerType === TriggerType.ENTITIES_LIMIT) { + this.entitiesLimitTemplateForm.get('triggerConfig.threshold').patchValue(this.ruleNotification.triggerConfig.threshold * 100, {emitEvent: false}); + } + if (this.ruleNotification.triggerType === TriggerType.RESOURCES_SHORTAGE) { + this.resourceUsageShortageTemplateForm.get('triggerConfig.cpuThreshold').patchValue(this.ruleNotification.triggerConfig.cpuThreshold * 100, {emitEvent: false}); + this.resourceUsageShortageTemplateForm.get('triggerConfig.ramThreshold').patchValue(this.ruleNotification.triggerConfig.ramThreshold * 100, {emitEvent: false}); + this.resourceUsageShortageTemplateForm.get('triggerConfig.storageThreshold').patchValue(this.ruleNotification.triggerConfig.storageThreshold * 100, {emitEvent: false}); + } } } @@ -438,6 +446,14 @@ export class RuleNotificationDialogComponent extends if (triggerType === TriggerType.DEVICE_ACTIVITY) { delete formValue.triggerConfig.filterByDevice; } + if (triggerType === TriggerType.ENTITIES_LIMIT) { + formValue.triggerConfig.threshold = formValue.triggerConfig.threshold / 100; + } + if (triggerType === TriggerType.RESOURCES_SHORTAGE) { + formValue.triggerConfig.cpuThreshold = formValue.triggerConfig.cpuThreshold / 100; + formValue.triggerConfig.ramThreshold = formValue.triggerConfig.ramThreshold / 100; + formValue.triggerConfig.storageThreshold = formValue.triggerConfig.storageThreshold / 100; + } formValue.recipientsConfig.triggerType = triggerType; formValue.triggerConfig.triggerType = triggerType; if (this.ruleNotification && !this.data.isCopy) { @@ -493,8 +509,7 @@ export class RuleNotificationDialogComponent extends } formatLabel(value: number): string { - const formatValue = (value * 100).toFixed(); - return `${formatValue}%`; + return `${value}%`; } private isSysAdmin(): boolean { diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 369bc08c15..63d7edd2a2 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -129,7 +129,7 @@ export interface NotificationRule extends Omit, 'la export type NotificationRuleTriggerConfig = Partial; + ApiUsageLimitNotificationRuleTriggerConfig & RateLimitsNotificationRuleTriggerConfig & ResourceUsageShortageNotificationRuleTriggerConfig>; export interface AlarmNotificationRuleTriggerConfig { alarmTypes?: Array; @@ -183,6 +183,12 @@ export interface EntitiesLimitNotificationRuleTriggerConfig { threshold: number; } +export interface ResourceUsageShortageNotificationRuleTriggerConfig { + cpuThreshold: number; + ramThreshold: number; + storageThreshold: number; +} + export interface ApiUsageLimitNotificationRuleTriggerConfig { apiFeatures: ApiFeature[]; notifyOn: ApiUsageStateValue[]; From f3a0d346bcf75ee94eb6f2df51b0bd8c859dbf7a Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 26 Feb 2025 16:48:30 +0200 Subject: [PATCH 08/14] UI: Refactoring --- .../rule-notification-dialog.component.html | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html index 0951acfea8..68122875c0 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -480,17 +480,18 @@ ignoreAuthorityFilter [allowedEntityTypes]="allowEntityTypeForEntitiesLimit"> -
+
notification.threshold
- + + %
@@ -598,45 +599,48 @@
notification.filter -
+
notification.cpu-threshold
- + + %
-
+
notification.ram-threshold
- + + %
-
+
notification.storage-threshold
- + + %
From f58608f94cdee03783626260b8261350392cbe7b Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 26 Feb 2025 17:26:52 +0200 Subject: [PATCH 09/14] Fix tupo --- .../server/dao/notification/DefaultNotifications.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index db6c26f483..2f209d8546 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -378,7 +378,7 @@ public class DefaultNotifications { .name("Resources shortage notification") .type(NotificationType.RESOURCES_SHORTAGE) .subject("Warning: ${resource} shortage") - .text("${resource} usage is at ${usage}%).") + .text("${resource} usage is at ${usage}%.") .icon("warning") .rule(DefaultRule.builder() .name("Resources shortage") From f7923bc0095e440c83ed37e0e3b598dfc54cd511 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 27 Feb 2025 10:21:27 +0200 Subject: [PATCH 10/14] Make tests more stable --- .../service/notification/NotificationRuleApiTest.java | 10 +++++----- ...ResourcesShortageNotificationRuleTriggerConfig.java | 1 - .../server/dao/notification/DefaultNotifications.java | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 20800f1bb9..31507758de 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -791,10 +791,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { public void testNotificationRuleProcessing_resourcesShortage() throws Exception { loginSysAdmin(); ResourcesShortageNotificationRuleTriggerConfig triggerConfig = ResourcesShortageNotificationRuleTriggerConfig.builder() - .resource(Resource.CPU.name()) .cpuThreshold(0.01f) - .ramThreshold(0.01f) - .storageThreshold(0.01f) + .ramThreshold(1f) + .storageThreshold(1f) .build(); createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); loginTenantAdmin(); @@ -805,15 +804,16 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { TimeUnit.SECONDS.sleep(5); - assertThat(getMyNotifications(false, 100)).size().isEqualTo(3); + assertThat(getMyNotifications(false, 100)).size().isOne(); } @Test public void testNotificationsDeduplication_resourcesShortage() throws Exception { loginSysAdmin(); ResourcesShortageNotificationRuleTriggerConfig triggerConfig = ResourcesShortageNotificationRuleTriggerConfig.builder() - .resource(Resource.CPU.name()) .cpuThreshold(0.1f) + .ramThreshold(1f) + .storageThreshold(1f) .build(); createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); loginTenantAdmin(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java index d542292113..e8ea6c9be5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/ResourcesShortageNotificationRuleTriggerConfig.java @@ -32,7 +32,6 @@ public class ResourcesShortageNotificationRuleTriggerConfig implements Notificat @Serial private static final long serialVersionUID = 339395299693241424L; - private String resource; @Max(1) private float cpuThreshold; // in percents @Max(1) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 2f209d8546..960cbfcd73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -382,7 +382,7 @@ public class DefaultNotifications { .icon("warning") .rule(DefaultRule.builder() .name("Resources shortage") - .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().resource(Resource.CPU.name()).cpuThreshold(0.8f).storageThreshold(0.8f).ramThreshold(0.8f).build()) + .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().cpuThreshold(0.8f).storageThreshold(0.8f).ramThreshold(0.8f).build()) .description("Send notification to system admins when resources shortage is running low") .build()) .color(RED_COLOR) From 923704768ff02ce81372bbcef49d3f27a47d4ac4 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 5 Mar 2025 10:25:23 +0200 Subject: [PATCH 11/14] Make tests more stable --- .../notification/NotificationRuleApiTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 31507758de..518df47b34 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -791,8 +791,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { public void testNotificationRuleProcessing_resourcesShortage() throws Exception { loginSysAdmin(); ResourcesShortageNotificationRuleTriggerConfig triggerConfig = ResourcesShortageNotificationRuleTriggerConfig.builder() - .cpuThreshold(0.01f) - .ramThreshold(1f) + .ramThreshold(0.01f) + .cpuThreshold(1f) .storageThreshold(1f) .build(); createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); @@ -811,8 +811,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { public void testNotificationsDeduplication_resourcesShortage() throws Exception { loginSysAdmin(); ResourcesShortageNotificationRuleTriggerConfig triggerConfig = ResourcesShortageNotificationRuleTriggerConfig.builder() - .cpuThreshold(0.1f) - .ramThreshold(1f) + .ramThreshold(0.01f) + .cpuThreshold(1f) .storageThreshold(1f) .build(); createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); @@ -821,7 +821,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(getMyNotifications(false, 100)).size().isZero(); for (int i = 0; i < 10; i++) { notificationRuleProcessor.process(ResourcesShortageTrigger.builder() - .resource(Resource.CPU) + .resource(Resource.RAM) .usage(15L) .build()); TimeUnit.MILLISECONDS.sleep(300); @@ -831,7 +831,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { // deduplication is 5 minute, no new message is exp notificationRuleProcessor.process(ResourcesShortageTrigger.builder() - .resource(Resource.CPU) + .resource(Resource.RAM) .usage(5L) .build()); await("").atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(getMyNotifications(false, 100)).size().isOne()); From 58c207ffdda0ca5b5bd6d39bc95087d0ed4e6c44 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 29 May 2025 11:35:07 +0300 Subject: [PATCH 12/14] Fix after review --- .../help/en_US/notification/resources_shortage.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md index eb4ef57f7a..74a5beb881 100644 --- a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md +++ b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md @@ -1,4 +1,4 @@ -#### Resource usage shortage notification templatization +#### Resources shortage notification templatization

@@ -9,16 +9,14 @@ See the available types and parameters below: Available template parameters: -* `cpuThreshold` - the CPU shortage threshold; -* `ramThreshold` - the RAM shortage threshold; -* `storageThreshold` - the Storage shortage threshold; +* `resource` - the shortage threshold; -Parameter names must be wrapped using `${...}`. For example: `${entityType}`. +Parameter names must be wrapped using `${...}`. For example: `${resource}`. You may also modify the value of the parameter with one of the suffixes: -* `upperCase`, for example - `${entityType:upperCase}` -* `lowerCase`, for example - `${entityType:lowerCase}` -* `capitalize`, for example - `${entityType:capitalize}` +* `upperCase`, for example - `${resource:upperCase}` +* `lowerCase`, for example - `${resource:lowerCase}` +* `capitalize`, for example - `${resource:capitalize}`
From 58ef5447eda8c5292c3e34a310278e6a9880a431 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 30 May 2025 15:20:50 +0300 Subject: [PATCH 13/14] Minor improvement --- .../system/DefaultSystemInfoService.java | 14 +++++------ .../notification/NotificationRuleApiTest.java | 24 +++++++++++-------- .../notification/DefaultNotifications.java | 2 +- .../en_US/notification/resources_shortage.md | 3 ++- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index 4cda9782c3..adc87957d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -95,7 +95,7 @@ public class DefaultSystemInfoService extends TbApplicationEventListener clusterSystemData = getSystemData(serviceInfoProvider.getServiceInfo()); clusterSystemData.forEach(data -> { - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(data.getCpuUsage()).build()); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(data.getMemoryUsage()).build()); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(data.getDiscUsage()).build()); + notificationRuleProcessor.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(data.getCpuUsage()).build()); + notificationRuleProcessor.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(data.getMemoryUsage()).build()); + notificationRuleProcessor.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(data.getDiscUsage()).build()); }); BasicTsKvEntry clusterDataKv = new BasicTsKvEntry(ts, new JsonDataEntry("clusterSystemData", JacksonUtil.toString(clusterSystemData))); doSave(Arrays.asList(new BasicTsKvEntry(ts, new BooleanDataEntry("clusterMode", true)), clusterDataKv)); @@ -200,17 +200,17 @@ public class DefaultSystemInfoService extends TbApplicationEventListener { long value = (long) v; tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", value))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(value).build()); + notificationRuleProcessor.process(ResourcesShortageTrigger.builder().resource(Resource.CPU).usage(value).build()); }); getMemoryUsage().ifPresent(v -> { long value = (long) v; tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", value))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(value).build()); + notificationRuleProcessor.process(ResourcesShortageTrigger.builder().resource(Resource.RAM).usage(value).build()); }); getDiscSpaceUsage().ifPresent(v -> { long value = (long) v; tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", value))); - notificationRule.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(value).build()); + notificationRuleProcessor.process(ResourcesShortageTrigger.builder().resource(Resource.STORAGE).usage(value).build()); }); getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v)))); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 518df47b34..dafcebc63c 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -94,6 +94,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -137,7 +138,7 @@ import static org.thingsboard.server.common.data.notification.rule.trigger.confi public class NotificationRuleApiTest extends AbstractNotificationApiTest { @SpyBean - private AlarmSubscriptionService alarmSubscriptionService;; + private AlarmSubscriptionService alarmSubscriptionService; @Autowired private DefaultSystemInfoService systemInfoService; @Autowired @@ -261,7 +262,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(info.getAlarmStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK); }); - clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate()); + clients.values().forEach(TbTestWebSocketClient::registerWaitForUpdate); alarmSubscriptionService.acknowledgeAlarm(tenantId, alarm.getId(), System.currentTimeMillis()); AlarmStatus expectedStatus = AlarmStatus.ACTIVE_ACK; AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL; @@ -645,7 +646,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { rule = saveNotificationRule(rule); NotificationRuleInfo ruleInfo = findNotificationRules().getData().get(0); - assertThat(ruleInfo.getId()).isEqualTo(ruleInfo.getId()); + assertThat(ruleInfo.getId()).isEqualTo(rule.getId()); assertThat(ruleInfo.getTemplateName()).isEqualTo(template.getName()); assertThat(ruleInfo.getDeliveryMethods()).containsOnly(deliveryMethods); } @@ -795,16 +796,17 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { .cpuThreshold(1f) .storageThreshold(1f) .build(); - createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); + createNotificationRule(triggerConfig, "Warning: ${resource} shortage", "${resource} shortage", createNotificationTarget(tenantAdminUserId).getId()); loginTenantAdmin(); Method method = DefaultSystemInfoService.class.getDeclaredMethod("saveCurrentMonolithSystemInfo"); method.setAccessible(true); method.invoke(systemInfoService); - TimeUnit.SECONDS.sleep(5); - - assertThat(getMyNotifications(false, 100)).size().isOne(); + await().atMost(10, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1); + Notification notification = getMyNotifications(false, 100).get(0); + assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage"); + assertThat(notification.getText()).isEqualTo("RAM shortage"); } @Test @@ -815,7 +817,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { .cpuThreshold(1f) .storageThreshold(1f) .build(); - createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId()); + createNotificationRule(triggerConfig, "Test", "${resource} shortage", createNotificationTarget(tenantAdminUserId).getId()); loginTenantAdmin(); assertThat(getMyNotifications(false, 100)).size().isZero(); @@ -826,8 +828,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { .build()); TimeUnit.MILLISECONDS.sleep(300); } - TimeUnit.SECONDS.sleep(5); - assertThat(getMyNotifications(false, 100)).size().isOne(); + await().atMost(10, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1); + Notification notification = getMyNotifications(false, 100).get(0); + assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage"); + assertThat(notification.getText()).isEqualTo("RAM shortage"); // deduplication is 5 minute, no new message is exp notificationRuleProcessor.process(ResourcesShortageTrigger.builder() diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 960cbfcd73..9b8b8b255e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -383,7 +383,7 @@ public class DefaultNotifications { .rule(DefaultRule.builder() .name("Resources shortage") .triggerConfig(ResourcesShortageNotificationRuleTriggerConfig.builder().cpuThreshold(0.8f).storageThreshold(0.8f).ramThreshold(0.8f).build()) - .description("Send notification to system admins when resources shortage is running low") + .description("Send notification to system admins on resource shortage") .build()) .color(RED_COLOR) .build(); diff --git a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md index 74a5beb881..9b469c0f85 100644 --- a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md +++ b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md @@ -9,7 +9,8 @@ See the available types and parameters below: Available template parameters: -* `resource` - the shortage threshold; +* `resource` - the resource name; +* `usage` - the resource usage value; Parameter names must be wrapped using `${...}`. For example: `${resource}`. You may also modify the value of the parameter with one of the suffixes: From 70f1f1834fcd749bc00be1f3d2a510cda4509e36 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 30 May 2025 15:22:03 +0300 Subject: [PATCH 14/14] Fix testNotificationsDeduplication_resourcesShortage --- .../server/service/notification/NotificationRuleApiTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index dafcebc63c..cf4f7d0836 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -817,7 +817,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { .cpuThreshold(1f) .storageThreshold(1f) .build(); - createNotificationRule(triggerConfig, "Test", "${resource} shortage", createNotificationTarget(tenantAdminUserId).getId()); + createNotificationRule(triggerConfig, "Warning: ${resource} shortage", "${resource} shortage", createNotificationTarget(tenantAdminUserId).getId());it a loginTenantAdmin(); assertThat(getMyNotifications(false, 100)).size().isZero();