Commit 9873115d8baaae032cabe3a3fb259cc3d7501f50

Authored by Viacheslav Klimov
Committed by Andrew Shvayka
1 parent a7d1b54b

Implement created alarms limiting

... ... @@ -130,3 +130,7 @@ DO $$
130 130 END;
131 131 $$;
132 132
  133 +
  134 +ALTER TABLE api_usage_state
  135 + ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32);
  136 +UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL;
... ...
... ... @@ -106,6 +106,8 @@ public abstract class BaseApiUsageState {
106 106 return apiUsageState.getEmailExecState();
107 107 case SMS:
108 108 return apiUsageState.getSmsExecState();
  109 + case ALARM:
  110 + return apiUsageState.getAlarmExecState();
109 111 default:
110 112 return ApiUsageStateValue.ENABLED;
111 113 }
... ... @@ -132,6 +134,9 @@ public abstract class BaseApiUsageState {
132 134 case SMS:
133 135 apiUsageState.setSmsExecState(value);
134 136 break;
  137 + case ALARM:
  138 + apiUsageState.setAlarmExecState(value);
  139 + break;
135 140 }
136 141 return !currentValue.equals(value);
137 142 }
... ...
... ... @@ -309,6 +309,8 @@ public class DefaultMailService implements MailService {
309 309 case EMAIL:
310 310 case SMS:
311 311 return "send";
  312 + case ALARM:
  313 + return "create";
312 314 default:
313 315 throw new RuntimeException("Not implemented!");
314 316 }
... ... @@ -327,6 +329,8 @@ public class DefaultMailService implements MailService {
327 329 case EMAIL:
328 330 case SMS:
329 331 return "sent";
  332 + case ALARM:
  333 + return "created";
330 334 default:
331 335 throw new RuntimeException("Not implemented!");
332 336 }
... ...
... ... @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.checkerframework.checker.nullness.qual.Nullable;
24 24 import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.stereotype.Service;
  26 +import org.thingsboard.server.common.data.ApiUsageRecordKey;
26 27 import org.thingsboard.server.common.data.alarm.Alarm;
27 28 import org.thingsboard.server.common.data.alarm.AlarmInfo;
28 29 import org.thingsboard.server.common.data.alarm.AlarmQuery;
... ... @@ -43,6 +44,8 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult;
43 44 import org.thingsboard.server.dao.alarm.AlarmService;
44 45 import org.thingsboard.server.gen.transport.TransportProtos;
45 46 import org.thingsboard.server.queue.discovery.PartitionService;
  47 +import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
  48 +import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
46 49 import org.thingsboard.server.service.queue.TbClusterService;
47 50 import org.thingsboard.server.service.subscription.SubscriptionManagerService;
48 51 import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
... ... @@ -58,12 +61,18 @@ import java.util.Optional;
58 61 public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
59 62
60 63 private final AlarmService alarmService;
  64 + private final TbApiUsageClient apiUsageClient;
  65 + private final TbApiUsageStateService apiUsageStateService;
61 66
62 67 public DefaultAlarmSubscriptionService(TbClusterService clusterService,
63 68 PartitionService partitionService,
64   - AlarmService alarmService) {
  69 + AlarmService alarmService,
  70 + TbApiUsageClient apiUsageClient,
  71 + TbApiUsageStateService apiUsageStateService) {
65 72 super(clusterService, partitionService);
66 73 this.alarmService = alarmService;
  74 + this.apiUsageClient = apiUsageClient;
  75 + this.apiUsageStateService = apiUsageStateService;
67 76 }
68 77
69 78 @Autowired(required = false)
... ... @@ -78,10 +87,19 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
78 87
79 88 @Override
80 89 public Alarm createOrUpdateAlarm(Alarm alarm) {
81   - AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
  90 + AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm,
  91 + () -> {
  92 + if (!apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled()) {
  93 + throw new IllegalStateException("Alarms creation is disabled due to API limits");
  94 + }
  95 + },
  96 + () -> {});
82 97 if (result.isSuccessful()) {
83 98 onAlarmUpdated(result);
84 99 }
  100 + if (result.isCreated()) {
  101 + apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
  102 + }
85 103 return result.getAlarm();
86 104 }
87 105
... ...
... ... @@ -26,17 +26,21 @@ import java.util.List;
26 26 public class AlarmOperationResult {
27 27 private final Alarm alarm;
28 28 private final boolean successful;
  29 + private final boolean created;
29 30 private final List<EntityId> propagatedEntitiesList;
30 31
31 32 public AlarmOperationResult(Alarm alarm, boolean successful) {
32   - this.alarm = alarm;
33   - this.successful = successful;
34   - this.propagatedEntitiesList = Collections.emptyList();
  33 + this(alarm, successful, Collections.emptyList());
35 34 }
36 35
37 36 public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) {
  37 + this(alarm, successful, false, propagatedEntitiesList);
  38 + }
  39 +
  40 + public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
38 41 this.alarm = alarm;
39 42 this.successful = successful;
  43 + this.created = created;
40 44 this.propagatedEntitiesList = propagatedEntitiesList;
41 45 }
42 46 }
... ...
... ... @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.page.PageData;
31 31 import org.thingsboard.server.common.data.query.AlarmData;
32 32 import org.thingsboard.server.common.data.query.AlarmDataPageLink;
33 33 import org.thingsboard.server.common.data.query.AlarmDataQuery;
  34 +import org.thingsboard.server.common.msg.queue.TbCallback;
34 35
35 36 import java.util.Collection;
36 37
... ... @@ -41,6 +42,8 @@ public interface AlarmService {
41 42
42 43 AlarmOperationResult createOrUpdateAlarm(Alarm alarm);
43 44
  45 + AlarmOperationResult createOrUpdateAlarm(Alarm alarm, Runnable onAlarmCreation, Runnable onAlarmUpdate);
  46 +
44 47 AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
45 48
46 49 ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
... ...
... ... @@ -23,7 +23,8 @@ public enum ApiFeature {
23 23 RE("ruleEngineApiState", "Rule Engine execution"),
24 24 JS("jsExecutionApiState", "JavaScript functions execution"),
25 25 EMAIL("emailApiState", "Email messages"),
26   - SMS("smsApiState", "SMS messages");
  26 + SMS("smsApiState", "SMS messages"),
  27 + ALARM("alarmApiState", "Created alarms");
27 28
28 29 @Getter
29 30 private final String apiStateKey;
... ...
... ... @@ -25,13 +25,16 @@ public enum ApiUsageRecordKey {
25 25 RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"),
26 26 JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"),
27 27 EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"),
28   - SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit");
  28 + SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"),
  29 + CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit");
  30 +
29 31 private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
30 32 private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
31 33 private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT};
32 34 private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT};
33 35 private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT};
34 36 private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT};
  37 + private static final ApiUsageRecordKey[] ALARM_RECORD_KEYS = {CREATED_ALARMS_COUNT};
35 38
36 39 @Getter
37 40 private final ApiFeature apiFeature;
... ... @@ -60,6 +63,8 @@ public enum ApiUsageRecordKey {
60 63 return EMAIL_RECORD_KEYS;
61 64 case SMS:
62 65 return SMS_RECORD_KEYS;
  66 + case ALARM:
  67 + return ALARM_RECORD_KEYS;
63 68 default:
64 69 return new ApiUsageRecordKey[]{};
65 70 }
... ...
... ... @@ -25,34 +25,21 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId;
25 25
26 26 @ToString
27 27 @EqualsAndHashCode(callSuper = true)
  28 +@Getter
  29 +@Setter
28 30 public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenantId {
29 31
30 32 private static final long serialVersionUID = 8250339805336035966L;
31 33
32   - @Getter
33   - @Setter
34 34 private TenantId tenantId;
35   - @Getter
36   - @Setter
37 35 private EntityId entityId;
38   - @Getter
39   - @Setter
40 36 private ApiUsageStateValue transportState;
41   - @Getter
42   - @Setter
43 37 private ApiUsageStateValue dbStorageState;
44   - @Getter
45   - @Setter
46 38 private ApiUsageStateValue reExecState;
47   - @Getter
48   - @Setter
49 39 private ApiUsageStateValue jsExecState;
50   - @Getter
51   - @Setter
52 40 private ApiUsageStateValue emailExecState;
53   - @Getter
54   - @Setter
55 41 private ApiUsageStateValue smsExecState;
  42 + private ApiUsageStateValue alarmExecState;
56 43
57 44 public ApiUsageState() {
58 45 super();
... ... @@ -72,6 +59,7 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
72 59 this.jsExecState = ur.getJsExecState();
73 60 this.emailExecState = ur.getEmailExecState();
74 61 this.smsExecState = ur.getSmsExecState();
  62 + this.alarmExecState = ur.getAlarmExecState();
75 63 }
76 64
77 65 public boolean isTransportEnabled() {
... ... @@ -97,4 +85,8 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
97 85 public boolean isSmsSendEnabled(){
98 86 return !ApiUsageStateValue.DISABLED.equals(smsExecState);
99 87 }
  88 +
  89 + public boolean isAlarmCreationEnabled() {
  90 + return alarmExecState != ApiUsageStateValue.DISABLED;
  91 + }
100 92 }
... ...
... ... @@ -50,6 +50,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
50 50 private int maxRuleNodeExecutionsPerMessage;
51 51 private long maxEmails;
52 52 private long maxSms;
  53 + private long maxCreatedAlarms;
53 54
54 55 private int defaultStorageTtlDays;
55 56
... ... @@ -72,6 +73,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
72 73 return maxEmails;
73 74 case SMS_EXEC_COUNT:
74 75 return maxSms;
  76 + case CREATED_ALARMS_COUNT:
  77 + return maxCreatedAlarms;
75 78 }
76 79 return 0L;
77 80 }
... ...
... ... @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
47 47 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
48 48 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
49 49 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
  50 +import org.thingsboard.server.common.msg.queue.TbCallback;
50 51 import org.thingsboard.server.dao.entity.AbstractEntityService;
51 52 import org.thingsboard.server.dao.entity.EntityService;
52 53 import org.thingsboard.server.dao.exception.DataValidationException;
... ... @@ -65,6 +66,7 @@ import java.util.Set;
65 66 import java.util.concurrent.ExecutionException;
66 67 import java.util.concurrent.ExecutorService;
67 68 import java.util.concurrent.Executors;
  69 +import java.util.function.Consumer;
68 70 import java.util.stream.Collectors;
69 71 import java.util.stream.Stream;
70 72
... ... @@ -102,6 +104,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
102 104
103 105 @Override
104 106 public AlarmOperationResult createOrUpdateAlarm(Alarm alarm) {
  107 + return createOrUpdateAlarm(alarm, () -> {}, () -> {});
  108 + }
  109 +
  110 + @Override
  111 + public AlarmOperationResult createOrUpdateAlarm(Alarm alarm, Runnable onAlarmCreation, Runnable onAlarmUpdate) {
105 112 alarmDataValidator.validate(alarm, Alarm::getTenantId);
106 113 try {
107 114 if (alarm.getStartTs() == 0L) {
... ... @@ -114,11 +121,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
114 121 if (alarm.getId() == null) {
115 122 Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
116 123 if (existing == null || existing.getStatus().isCleared()) {
  124 + onAlarmCreation.run();
117 125 return createAlarm(alarm);
118 126 } else {
  127 + onAlarmUpdate.run();
119 128 return updateAlarm(existing, alarm);
120 129 }
121 130 } else {
  131 + onAlarmUpdate.run();
122 132 return updateAlarm(alarm).get();
123 133 }
124 134 } catch (ExecutionException | InterruptedException e) {
... ... @@ -159,7 +169,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
159 169 log.debug("New Alarm : {}", alarm);
160 170 Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
161 171 List<EntityId> propagatedEntitiesList = createAlarmRelations(saved);
162   - return new AlarmOperationResult(saved, true, propagatedEntitiesList);
  172 + return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
163 173 }
164 174
165 175 private List<EntityId> createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException {
... ...
... ... @@ -465,6 +465,7 @@ public class ModelConstants {
465 465 public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec";
466 466 public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec";
467 467 public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec";
  468 + public static final String API_USAGE_STATE_ALARM_EXEC_COLUMN = "alarm_exec";
468 469
469 470 /**
470 471 * Resource constants.
... ...
... ... @@ -69,6 +69,9 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
69 69 @Enumerated(EnumType.STRING)
70 70 @Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN)
71 71 private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED;
  72 + @Enumerated(EnumType.STRING)
  73 + @Column(name = ModelConstants.API_USAGE_STATE_ALARM_EXEC_COLUMN)
  74 + private ApiUsageStateValue alarmExecState = ApiUsageStateValue.ENABLED;
72 75
73 76 public ApiUsageStateEntity() {
74 77 }
... ... @@ -91,6 +94,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
91 94 this.jsExecState = ur.getJsExecState();
92 95 this.emailExecState = ur.getEmailExecState();
93 96 this.smsExecState = ur.getSmsExecState();
  97 + this.alarmExecState = ur.getAlarmExecState();
94 98 }
95 99
96 100 @Override
... ... @@ -109,6 +113,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
109 113 ur.setJsExecState(jsExecState);
110 114 ur.setEmailExecState(emailExecState);
111 115 ur.setSmsExecState(smsExecState);
  116 + ur.setAlarmExecState(alarmExecState);
112 117 return ur;
113 118 }
114 119
... ...
... ... @@ -90,6 +90,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
90 90 apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
91 91 apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED);
92 92 apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED);
  93 + apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED);
93 94 apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId);
94 95
95 96 ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState);
... ... @@ -107,6 +108,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
107 108 new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
108 109 apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
109 110 new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
  111 + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
  112 + new StringDataEntry(ApiFeature.ALARM.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
110 113 tsService.save(tenantId, saved.getId(), apiUsageStates, 0L);
111 114
112 115 if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) {
... ...
... ... @@ -454,6 +454,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
454 454 js_exec varchar(32),
455 455 email_exec varchar(32),
456 456 sms_exec varchar(32),
  457 + alarm_exec varchar(32),
457 458 CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
458 459 );
459 460
... ...
... ... @@ -491,6 +491,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
491 491 js_exec varchar(32),
492 492 email_exec varchar(32),
493 493 sms_exec varchar(32),
  494 + alarm_exec varchar(32),
494 495 CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
495 496 );
496 497
... ...