diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index 43ef244a14..4fec49130a 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -130,3 +130,7 @@ DO $$ END; $$; + +ALTER TABLE api_usage_state + ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32); +UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java index f22fd1c6e7..1c392913f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java @@ -106,6 +106,8 @@ public abstract class BaseApiUsageState { return apiUsageState.getEmailExecState(); case SMS: return apiUsageState.getSmsExecState(); + case ALARM: + return apiUsageState.getAlarmExecState(); default: return ApiUsageStateValue.ENABLED; } @@ -132,6 +134,9 @@ public abstract class BaseApiUsageState { case SMS: apiUsageState.setSmsExecState(value); break; + case ALARM: + apiUsageState.setAlarmExecState(value); + break; } return !currentValue.equals(value); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index b4c1058616..d11f2bd63b 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -309,6 +309,8 @@ public class DefaultMailService implements MailService { case EMAIL: case SMS: return "send"; + case ALARM: + return "create"; default: throw new RuntimeException("Not implemented!"); } @@ -327,6 +329,8 @@ public class DefaultMailService implements MailService { case EMAIL: case SMS: return "sent"; + case ALARM: + return "created"; default: throw new RuntimeException("Not implemented!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 82700ff064..abe6807821 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -43,6 +44,8 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; @@ -58,12 +61,18 @@ import java.util.Optional; public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { private final AlarmService alarmService; + private final TbApiUsageClient apiUsageClient; + private final TbApiUsageStateService apiUsageStateService; public DefaultAlarmSubscriptionService(TbClusterService clusterService, PartitionService partitionService, - AlarmService alarmService) { + AlarmService alarmService, + TbApiUsageClient apiUsageClient, + TbApiUsageStateService apiUsageStateService) { super(clusterService, partitionService); this.alarmService = alarmService; + this.apiUsageClient = apiUsageClient; + this.apiUsageStateService = apiUsageStateService; } @Autowired(required = false) @@ -78,10 +87,19 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService @Override public Alarm createOrUpdateAlarm(Alarm alarm) { - AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); + AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, + () -> { + if (!apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled()) { + throw new IllegalStateException("Alarms creation is disabled due to API limits"); + } + }, + () -> {}); if (result.isSuccessful()) { onAlarmUpdated(result); } + if (result.isCreated()) { + apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); + } return result.getAlarm(); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index 14f6572697..7515f31615 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -26,17 +26,21 @@ import java.util.List; public class AlarmOperationResult { private final Alarm alarm; private final boolean successful; + private final boolean created; private final List propagatedEntitiesList; public AlarmOperationResult(Alarm alarm, boolean successful) { - this.alarm = alarm; - this.successful = successful; - this.propagatedEntitiesList = Collections.emptyList(); + this(alarm, successful, Collections.emptyList()); } public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList) { + this(alarm, successful, false, propagatedEntitiesList); + } + + public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList) { this.alarm = alarm; this.successful = successful; + this.created = created; this.propagatedEntitiesList = propagatedEntitiesList; } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 2bca57b1b0..b9067cdd43 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataPageLink; import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.common.msg.queue.TbCallback; import java.util.Collection; @@ -41,6 +42,8 @@ public interface AlarmService { AlarmOperationResult createOrUpdateAlarm(Alarm alarm); + AlarmOperationResult createOrUpdateAlarm(Alarm alarm, Runnable onAlarmCreation, Runnable onAlarmUpdate); + AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId); ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index cb367b0b93..37206d924a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -23,7 +23,8 @@ public enum ApiFeature { RE("ruleEngineApiState", "Rule Engine execution"), JS("jsExecutionApiState", "JavaScript functions execution"), EMAIL("emailApiState", "Email messages"), - SMS("smsApiState", "SMS messages"); + SMS("smsApiState", "SMS messages"), + ALARM("alarmApiState", "Created alarms"); @Getter private final String apiStateKey; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index f43fe9bf8f..649e62fd42 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -25,13 +25,16 @@ public enum ApiUsageRecordKey { RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), - SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"); + SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"), + CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit"); + private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT}; private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT}; + private static final ApiUsageRecordKey[] ALARM_RECORD_KEYS = {CREATED_ALARMS_COUNT}; @Getter private final ApiFeature apiFeature; @@ -60,6 +63,8 @@ public enum ApiUsageRecordKey { return EMAIL_RECORD_KEYS; case SMS: return SMS_RECORD_KEYS; + case ALARM: + return ALARM_RECORD_KEYS; default: return new ApiUsageRecordKey[]{}; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 4a7067d4df..b8cad06037 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -25,34 +25,21 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId; @ToString @EqualsAndHashCode(callSuper = true) +@Getter +@Setter public class ApiUsageState extends BaseData implements HasTenantId { private static final long serialVersionUID = 8250339805336035966L; - @Getter - @Setter private TenantId tenantId; - @Getter - @Setter private EntityId entityId; - @Getter - @Setter private ApiUsageStateValue transportState; - @Getter - @Setter private ApiUsageStateValue dbStorageState; - @Getter - @Setter private ApiUsageStateValue reExecState; - @Getter - @Setter private ApiUsageStateValue jsExecState; - @Getter - @Setter private ApiUsageStateValue emailExecState; - @Getter - @Setter private ApiUsageStateValue smsExecState; + private ApiUsageStateValue alarmExecState; public ApiUsageState() { super(); @@ -72,6 +59,7 @@ public class ApiUsageState extends BaseData implements HasTenan this.jsExecState = ur.getJsExecState(); this.emailExecState = ur.getEmailExecState(); this.smsExecState = ur.getSmsExecState(); + this.alarmExecState = ur.getAlarmExecState(); } public boolean isTransportEnabled() { @@ -97,4 +85,8 @@ public class ApiUsageState extends BaseData implements HasTenan public boolean isSmsSendEnabled(){ return !ApiUsageStateValue.DISABLED.equals(smsExecState); } + + public boolean isAlarmCreationEnabled() { + return alarmExecState != ApiUsageStateValue.DISABLED; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 72713f12d3..b9bd72b0db 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -50,6 +50,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private int maxRuleNodeExecutionsPerMessage; private long maxEmails; private long maxSms; + private long maxCreatedAlarms; private int defaultStorageTtlDays; @@ -72,6 +73,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura return maxEmails; case SMS_EXEC_COUNT: return maxSms; + case CREATED_ALARMS_COUNT: + return maxCreatedAlarms; } return 0L; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index b40993b76b..a4290636f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -65,6 +66,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -102,6 +104,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmOperationResult createOrUpdateAlarm(Alarm alarm) { + return createOrUpdateAlarm(alarm, () -> {}, () -> {}); + } + + @Override + public AlarmOperationResult createOrUpdateAlarm(Alarm alarm, Runnable onAlarmCreation, Runnable onAlarmUpdate) { alarmDataValidator.validate(alarm, Alarm::getTenantId); try { if (alarm.getStartTs() == 0L) { @@ -114,11 +121,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (alarm.getId() == null) { Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); if (existing == null || existing.getStatus().isCleared()) { + onAlarmCreation.run(); return createAlarm(alarm); } else { + onAlarmUpdate.run(); return updateAlarm(existing, alarm); } } else { + onAlarmUpdate.run(); return updateAlarm(alarm).get(); } } catch (ExecutionException | InterruptedException e) { @@ -159,7 +169,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ log.debug("New Alarm : {}", alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); List propagatedEntitiesList = createAlarmRelations(saved); - return new AlarmOperationResult(saved, true, propagatedEntitiesList); + return new AlarmOperationResult(saved, true, true, propagatedEntitiesList); } private List createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 3f6058d6e5..0bc346e36c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -465,6 +465,7 @@ public class ModelConstants { public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec"; public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec"; + public static final String API_USAGE_STATE_ALARM_EXEC_COLUMN = "alarm_exec"; /** * Resource constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index fd069819a2..40b56c0095 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -69,6 +69,9 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Enumerated(EnumType.STRING) @Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN) private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_ALARM_EXEC_COLUMN) + private ApiUsageStateValue alarmExecState = ApiUsageStateValue.ENABLED; public ApiUsageStateEntity() { } @@ -91,6 +94,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.jsExecState = ur.getJsExecState(); this.emailExecState = ur.getEmailExecState(); this.smsExecState = ur.getSmsExecState(); + this.alarmExecState = ur.getAlarmExecState(); } @Override @@ -109,6 +113,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements ur.setJsExecState(jsExecState); ur.setEmailExecState(emailExecState); ur.setSmsExecState(smsExecState); + ur.setAlarmExecState(alarmExecState); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 7f928d6c41..880f507b44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -90,6 +90,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); @@ -107,6 +108,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.ALARM.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index dd33c5b85c..fcb7f82ed0 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -454,6 +454,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( js_exec varchar(32), email_exec varchar(32), sms_exec varchar(32), + alarm_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 86111989f2..30b9a3dbe7 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -491,6 +491,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( js_exec varchar(32), email_exec varchar(32), sms_exec varchar(32), + alarm_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) );