Implement created alarms limiting

This commit is contained in:
Viacheslav Klimov 2021-05-11 16:17:37 +03:00 committed by Andrew Shvayka
parent a7d1b54bb4
commit 9873115d8b
16 changed files with 84 additions and 24 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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!");
}

View File

@ -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();
}

View File

@ -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<EntityId> 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<EntityId> propagatedEntitiesList) {
this(alarm, successful, false, propagatedEntitiesList);
}
public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
this.alarm = alarm;
this.successful = successful;
this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList;
}
}

View File

@ -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<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);

View File

@ -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;

View File

@ -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[]{};
}

View File

@ -25,34 +25,21 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId;
@ToString
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
public class ApiUsageState extends BaseData<ApiUsageStateId> 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<ApiUsageStateId> 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<ApiUsageStateId> implements HasTenan
public boolean isSmsSendEnabled(){
return !ApiUsageStateValue.DISABLED.equals(smsExecState);
}
public boolean isAlarmCreationEnabled() {
return alarmExecState != ApiUsageStateValue.DISABLED;
}
}

View File

@ -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;
}

View File

@ -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<EntityId> propagatedEntitiesList = createAlarmRelations(saved);
return new AlarmOperationResult(saved, true, propagatedEntitiesList);
return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
}
private List<EntityId> createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException {

View File

@ -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.

View File

@ -69,6 +69,9 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> 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<ApiUsageState> 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<ApiUsageState> implements
ur.setJsExecState(jsExecState);
ur.setEmailExecState(emailExecState);
ur.setSmsExecState(smsExecState);
ur.setAlarmExecState(alarmExecState);
return ur;
}

View File

@ -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)) {

View File

@ -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)
);

View File

@ -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)
);