Commit 9873115d8baaae032cabe3a3fb259cc3d7501f50
Committed by
Andrew Shvayka
1 parent
a7d1b54b
Implement created alarms limiting
Showing
16 changed files
with
84 additions
and
24 deletions
... | ... | @@ -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)) { | ... | ... |