Browse Source

Improvements to API State

pull/3688/head
Andrii Shvaika 6 years ago
parent
commit
0e0ab6ca39
  1. 5
      application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java
  2. 92
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  3. 7
      application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java
  4. 43
      application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java
  5. 27
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  8. 22
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  9. 1
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java
  10. 30
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java
  11. 45
      common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java
  12. 1
      common/queue/src/main/proto/queue.proto
  13. 9
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  14. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  15. 11
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java
  16. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java
  17. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java
  18. 17
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  19. 26
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java
  20. 4
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  21. 19
      dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java
  22. 4
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  23. 4
      dao/src/main/resources/sql/schema-entities-hsql.sql
  24. 4
      dao/src/main/resources/sql/schema-entities.sql
  25. 13
      dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java

5
application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java

@ -0,0 +1,5 @@
package org.thingsboard.server.service.apiusage;
public enum ApiFeature {
TRANSPORT, DB, RE, JS
}

92
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -33,15 +34,18 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.profile.TbTenantProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -57,12 +61,17 @@ import java.util.concurrent.locks.ReentrantLock;
public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
public static final String HOURLY = "HOURLY_"; public static final String HOURLY = "HOURLY_";
private final TbClusterService clusterService;
private final PartitionService partitionService; private final PartitionService partitionService;
private final ApiUsageStateService apiUsageStateService; private final ApiUsageStateService apiUsageStateService;
private final TimeseriesService tsService; private final TimeseriesService tsService;
private final SchedulerComponent scheduler; private final SchedulerComponent scheduler;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final Map<TenantId, TenantApiUsageState> tenantStates = new ConcurrentHashMap<>();
// Tenants that should be processed on this server
private final Map<TenantId, TenantApiUsageState> myTenantStates = new ConcurrentHashMap<>();
// Tenants that should be processed on other servers
private final Map<TenantId, ApiUsageState> otherTenantStates = new ConcurrentHashMap<>();
@Value("${usage.stats.report.enabled:true}") @Value("${usage.stats.report.enabled:true}")
private boolean enabled; private boolean enabled;
@ -72,7 +81,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
private final Lock updateLock = new ReentrantLock(); private final Lock updateLock = new ReentrantLock();
public DefaultTbApiUsageStateService(PartitionService partitionService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { public DefaultTbApiUsageStateService(TbClusterService clusterService,
PartitionService partitionService,
ApiUsageStateService apiUsageStateService,
TimeseriesService tsService,
SchedulerComponent scheduler,
TbTenantProfileCache tenantProfileCache) {
this.clusterService = clusterService;
this.partitionService = partitionService; this.partitionService = partitionService;
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;
this.tsService = tsService; this.tsService = tsService;
@ -93,7 +108,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
TenantApiUsageState tenantState; TenantApiUsageState tenantState;
List<TsKvEntry> updatedEntries; List<TsKvEntry> updatedEntries;
boolean stateUpdated = false; Map<ApiFeature, Boolean> result = new HashMap<>();
updateLock.lock(); updateLock.lock();
try { try {
tenantState = getOrFetchState(tenantId); tenantState = getOrFetchState(tenantId);
@ -110,32 +125,49 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue)));
long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue());
updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue)));
stateUpdated |= tenantState.checkStateUpdatedDueToThreshold(recordKey); Pair<ApiFeature, Boolean> update = tenantState.checkStateUpdatedDueToThreshold(recordKey);
if (update != null) {
result.put(update.getFirst(), update.getSecond());
}
} }
} finally { } finally {
updateLock.unlock(); updateLock.unlock();
} }
tsService.save(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L); tsService.save(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L);
if (stateUpdated) { if (!result.isEmpty()) {
// Save new state into the database; persistAndNotify(tenantState, result);
apiUsageStateService.update(tenantState.getApiUsageState());
//TODO: clear cache on cluster repartition.
//TODO: update profiles on tenant and profile updates.
//TODO: broadcast to everyone notifications about enabled/disabled features.
} }
} }
@Override @Override
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) {
tenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition());
otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition());
} }
} }
@Override @Override
public TenantApiUsageState getApiUsageState(TenantId tenantId) { public ApiUsageState getApiUsageState(TenantId tenantId) {
//We should always get it from the map of from the database; if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
return null; TenantApiUsageState state = getOrFetchState(tenantId);
return state.getApiUsageState();
} else {
ApiUsageState state = otherTenantStates.get(tenantId);
if (state == null) {
updateLock.lock();
try {
state = otherTenantStates.get(tenantId);
if (state == null) {
state = apiUsageStateService.findTenantApiUsageState(tenantId);
otherTenantStates.put(tenantId, state);
}
} finally {
updateLock.unlock();
}
}
return state;
}
} }
@Override @Override
@ -143,7 +175,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId);
updateLock.lock(); updateLock.lock();
try { try {
tenantStates.values().forEach(state -> { myTenantStates.values().forEach(state -> {
if (tenantProfile.getId().equals(state.getTenantProfileId())) { if (tenantProfile.getId().equals(state.getTenantProfileId())) {
updateTenantState(state, tenantProfile); updateTenantState(state, tenantProfile);
} }
@ -158,7 +190,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
updateLock.lock(); updateLock.lock();
try { try {
TenantApiUsageState state = tenantStates.get(tenantId); TenantApiUsageState state = myTenantStates.get(tenantId);
if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) { if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) {
updateTenantState(state, tenantProfile); updateTenantState(state, tenantProfile);
} }
@ -167,12 +199,28 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
} }
} }
@Override
public void onApiUsageStateUpdate(TenantId tenantId) {
}
private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) {
state.setTenantProfileData(tenantProfile.getProfileData()); state.setTenantProfileData(tenantProfile.getProfileData());
if (state.checkStateUpdatedDueToThresholds()) { Map<ApiFeature, Boolean> result = state.checkStateUpdatedDueToThresholds();
apiUsageStateService.update(state.getApiUsageState()); if (!result.isEmpty()) {
//TODO: send notification to cluster; persistAndNotify(state, result);
}
}
private void persistAndNotify(TenantApiUsageState state, Map<ApiFeature, Boolean> result) {
// TODO:
// 1. Broadcast to everyone notifications about enabled/disabled features.
// 2. Report rule engine and js executor metrics
// 4. UI for configuration of the thresholds
// 5. Max rule node executions per message.
apiUsageStateService.update(state.getApiUsageState());
if (result.containsKey(ApiFeature.TRANSPORT)) {
clusterService.onApiStateChange(state.getApiUsageState(), null);
} }
} }
@ -180,7 +228,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
updateLock.lock(); updateLock.lock();
try { try {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
tenantStates.values().forEach(state -> { myTenantStates.values().forEach(state -> {
if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) {
state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
} }
@ -191,7 +239,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
} }
private TenantApiUsageState getOrFetchState(TenantId tenantId) { private TenantApiUsageState getOrFetchState(TenantId tenantId) {
TenantApiUsageState tenantState = tenantStates.get(tenantId); TenantApiUsageState tenantState = myTenantStates.get(tenantId);
if (tenantState == null) { if (tenantState == null) {
ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId);
if (dbStateEntity == null) { if (dbStateEntity == null) {
@ -223,7 +271,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
} }
} }
} }
tenantStates.put(tenantId, tenantState); myTenantStates.put(tenantId, tenantState);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e);
} }

7
application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -16,6 +16,7 @@
package org.thingsboard.server.service.apiusage; package org.thingsboard.server.service.apiusage;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -27,9 +28,11 @@ public interface TbApiUsageStateService extends ApplicationListener<PartitionCha
void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback); void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback);
TenantApiUsageState getApiUsageState(TenantId tenantId); ApiUsageState getApiUsageState(TenantId tenantId);
void onTenantProfileUpdate(TenantProfileId tenantProfileId); void onTenantProfileUpdate(TenantProfileId tenantProfileId);
void onTenantUpdate(TenantId tenantId); void onTenantUpdate(TenantId tenantId);
void onApiUsageStateUpdate(TenantId tenantId);
} }

43
application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -17,14 +17,17 @@ package org.thingsboard.server.service.apiusage;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.util.Pair;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.TenantProfileData;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -103,11 +106,17 @@ public class TenantApiUsageState {
return Long.parseLong((String) threshold); return Long.parseLong((String) threshold);
} else if (threshold instanceof Long) { } else if (threshold instanceof Long) {
return (Long) threshold; return (Long) threshold;
} else if (threshold instanceof Integer) {
return (Integer) threshold;
} }
} }
return 0L; return 0L;
} }
public TenantId getTenantId() {
return apiUsageState.getTenantId();
}
public EntityId getEntityId() { public EntityId getEntityId() {
return apiUsageState.getEntityId(); return apiUsageState.getEntityId();
} }
@ -121,7 +130,7 @@ public class TenantApiUsageState {
} }
public boolean isRuleEngineEnabled() { public boolean isRuleEngineEnabled() {
return apiUsageState.isRuleEngineEnabled(); return apiUsageState.isReExecEnabled();
} }
public boolean isJsExecEnabled() { public boolean isJsExecEnabled() {
@ -137,7 +146,7 @@ public class TenantApiUsageState {
} }
public void setRuleEngineEnabled(boolean ruleEngineEnabled) { public void setRuleEngineEnabled(boolean ruleEngineEnabled) {
apiUsageState.setRuleEngineEnabled(ruleEngineEnabled); apiUsageState.setReExecEnabled(ruleEngineEnabled);
} }
public void setJsExecEnabled(boolean jsExecEnabled) { public void setJsExecEnabled(boolean jsExecEnabled) {
@ -147,7 +156,6 @@ public class TenantApiUsageState {
public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) {
switch (recordKey) { switch (recordKey) {
case MSG_COUNT: case MSG_COUNT:
case MSG_BYTES_COUNT:
case DP_TRANSPORT_COUNT: case DP_TRANSPORT_COUNT:
return isTransportEnabled(); return isTransportEnabled();
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
@ -161,38 +169,47 @@ public class TenantApiUsageState {
} }
} }
public boolean setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { public ApiFeature setFeatureValue(ApiUsageRecordKey recordKey, boolean value) {
ApiFeature feature = null;
boolean currentValue = isFeatureEnabled(recordKey); boolean currentValue = isFeatureEnabled(recordKey);
switch (recordKey) { switch (recordKey) {
case MSG_COUNT: case MSG_COUNT:
case MSG_BYTES_COUNT:
case DP_TRANSPORT_COUNT: case DP_TRANSPORT_COUNT:
feature = ApiFeature.TRANSPORT;
setTransportEnabled(value); setTransportEnabled(value);
break; break;
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
feature = ApiFeature.RE;
setRuleEngineEnabled(value); setRuleEngineEnabled(value);
break; break;
case DP_STORAGE_COUNT: case DP_STORAGE_COUNT:
feature = ApiFeature.DB;
setDbStorageEnabled(value); setDbStorageEnabled(value);
break; break;
case JS_EXEC_COUNT: case JS_EXEC_COUNT:
feature = ApiFeature.JS;
setJsExecEnabled(value); setJsExecEnabled(value);
break; break;
} }
return currentValue == value; return currentValue == value ? null : feature;
} }
public boolean checkStateUpdatedDueToThresholds() { public Map<ApiFeature, Boolean> checkStateUpdatedDueToThresholds() {
boolean update = false; Map<ApiFeature, Boolean> result = new HashMap<>();
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
update |= checkStateUpdatedDueToThreshold(key); Pair<ApiFeature, Boolean> featureUpdate = checkStateUpdatedDueToThreshold(key);
if (featureUpdate != null) {
result.put(featureUpdate.getFirst(), featureUpdate.getSecond());
}
} }
return update; return result;
} }
public boolean checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { public Pair<ApiFeature, Boolean> checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) {
long value = get(recordKey); long value = get(recordKey);
long threshold = getProfileThreshold(recordKey); long threshold = getProfileThreshold(recordKey);
return setFeatureValue(recordKey, threshold == 0 || value < threshold); boolean featureValue = threshold == 0 || value < threshold;
ApiFeature feature = setFeatureValue(recordKey, featureValue);
return feature != null ? Pair.of(feature, featureValue) : null;
} }
} }

27
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
@ -47,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotifica
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
@ -206,6 +208,12 @@ public class DefaultTbClusterService implements TbClusterService {
onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback);
} }
@Override
public void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback) {
onEntityChange(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback);
broadcast(new ComponentLifecycleMsg(apiUsageState.getTenantId(), apiUsageState.getId(), ComponentLifecycleEvent.UPDATED));
}
@Override @Override
public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) {
onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback);
@ -221,13 +229,14 @@ public class DefaultTbClusterService implements TbClusterService {
onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback);
} }
public <T extends HasName> void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { public <T> void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) {
log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entity.getName()); String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName();
log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName);
TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder()
.setEntityType(entityid.getEntityType().name()) .setEntityType(entityid.getEntityType().name())
.setData(ByteString.copyFrom(encodingService.encode(entity))).build(); .setData(ByteString.copyFrom(encodingService.encode(entity))).build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build();
broadcast(transportMsg); broadcast(transportMsg, callback);
} }
private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) {
@ -238,15 +247,16 @@ public class DefaultTbClusterService implements TbClusterService {
.setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .setEntityIdLSB(entityId.getId().getLeastSignificantBits())
.build(); .build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build();
broadcast(transportMsg); broadcast(transportMsg, callback);
} }
private void broadcast(ToTransportMsg transportMsg) { private void broadcast(ToTransportMsg transportMsg, TbQueueCallback callback) {
TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer();
Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT); Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
TbQueueCallback proxyCallback = callback != null ? new MultipleTbQueueCallbackWrapper(tbTransportServices.size(), callback) : null;
for (String transportServiceId : tbTransportServices) { for (String transportServiceId : tbTransportServices) {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId); TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);
toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null); toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), proxyCallback);
toTransportNfs.incrementAndGet(); toTransportNfs.incrementAndGet();
} }
} }
@ -256,7 +266,8 @@ public class DefaultTbClusterService implements TbClusterService {
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();
Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE));
if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)
|| msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)) { || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)
|| msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) {
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
for (String serviceId : tbCoreServices) { for (String serviceId : tbCoreServices) {

3
application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.queue; package org.thingsboard.server.service.queue;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
@ -63,4 +64,6 @@ public interface TbClusterService {
void onTenantChange(Tenant tenant, TbQueueCallback callback); void onTenantChange(Tenant tenant, TbQueueCallback callback);
void onTenantDelete(Tenant tenant, TbQueueCallback callback); void onTenantDelete(Tenant tenant, TbQueueCallback callback);
void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback);
} }

4
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -161,6 +161,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId())); deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId())); deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.API_USAGE_STATE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
apiUsageStateService.onApiUsageStateUpdate(componentLifecycleMsg.getTenantId());
} }
} }
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg); log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);

22
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -25,6 +25,7 @@ import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
@ -51,7 +52,6 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -68,6 +68,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.profile.TbTenantProfileCache;
@ -92,6 +93,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final TbApiUsageStateService apiUsageStateService;
private final DeviceService deviceService; private final DeviceService deviceService;
private final RelationService relationService; private final RelationService relationService;
private final DeviceCredentialsService deviceCredentialsService; private final DeviceCredentialsService deviceCredentialsService;
@ -104,13 +106,14 @@ public class DefaultTransportApiService implements TransportApiService {
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache, public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache,
TbTenantProfileCache tenantProfileCache, DeviceService deviceService, TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, DeviceService deviceService,
RelationService relationService, DeviceCredentialsService deviceCredentialsService, RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService) { DeviceProvisionService deviceProvisionService) {
this.deviceProfileCache = deviceProfileCache; this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
this.apiUsageStateService = apiUsageStateService;
this.deviceService = deviceService; this.deviceService = deviceService;
this.relationService = relationService; this.relationService = relationService;
this.deviceCredentialsService = deviceCredentialsService; this.deviceCredentialsService = deviceCredentialsService;
@ -316,18 +319,21 @@ public class DefaultTransportApiService implements TransportApiService {
private ListenableFuture<TransportApiResponseMsg> handle(GetEntityProfileRequestMsg requestMsg) { private ListenableFuture<TransportApiResponseMsg> handle(GetEntityProfileRequestMsg requestMsg) {
EntityType entityType = EntityType.valueOf(requestMsg.getEntityType()); EntityType entityType = EntityType.valueOf(requestMsg.getEntityType());
UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()); UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB());
ByteString data; GetEntityProfileResponseMsg.Builder builder = GetEntityProfileResponseMsg.newBuilder();
if (entityType.equals(EntityType.DEVICE_PROFILE)) { if (entityType.equals(EntityType.DEVICE_PROFILE)) {
DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid); DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid);
DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId);
data = ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)); builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
} else if (entityType.equals(EntityType.TENANT)) { } else if (entityType.equals(EntityType.TENANT)) {
TenantProfile tenantProfile = tenantProfileCache.get(new TenantId(entityUuid)); TenantId tenantId = new TenantId(entityUuid);
data = ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
ApiUsageState state = apiUsageStateService.getApiUsageState(tenantId);
builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)));
builder.setApiState(ByteString.copyFrom(dataDecodingEncodingService.encode(state)));
} else { } else {
throw new RuntimeException("Invalid entity profile request: " + entityType); throw new RuntimeException("Invalid entity profile request: " + entityType);
} }
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(GetEntityProfileResponseMsg.newBuilder().setData(data).build()).build()); return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build());
} }
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {

1
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java

@ -18,7 +18,6 @@ package org.thingsboard.server.common.data;
public enum ApiUsageRecordKey { public enum ApiUsageRecordKey {
MSG_COUNT, MSG_COUNT,
MSG_BYTES_COUNT,
DP_TRANSPORT_COUNT, DP_TRANSPORT_COUNT,
DP_STORAGE_COUNT, DP_STORAGE_COUNT,
RE_EXEC_COUNT, RE_EXEC_COUNT,

30
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java

@ -29,18 +29,24 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
private static final long serialVersionUID = 8250339805336035966L; private static final long serialVersionUID = 8250339805336035966L;
@Getter @Setter @Getter
@Setter
private TenantId tenantId; private TenantId tenantId;
@Getter @Setter @Getter
@Setter
private EntityId entityId; private EntityId entityId;
@Getter @Setter @Getter
private boolean transportEnabled; @Setter
@Getter @Setter private boolean transportEnabled = true;
private boolean dbStorageEnabled; @Getter
@Getter @Setter @Setter
private boolean ruleEngineEnabled; private boolean dbStorageEnabled = true;
@Getter @Setter @Getter
private boolean jsExecEnabled; @Setter
private boolean reExecEnabled = true;
@Getter
@Setter
private boolean jsExecEnabled = true;
public ApiUsageState() { public ApiUsageState() {
super(); super();
@ -54,5 +60,9 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
super(ur); super(ur);
this.tenantId = ur.getTenantId(); this.tenantId = ur.getTenantId();
this.entityId = ur.getEntityId(); this.entityId = ur.getEntityId();
this.transportEnabled = ur.isTransportEnabled();
this.dbStorageEnabled = ur.isDbStorageEnabled();
this.reExecEnabled = ur.isReExecEnabled();
this.jsExecEnabled = ur.isJsExecEnabled();
} }
} }

45
common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2020 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.queue.common;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import java.util.concurrent.atomic.AtomicInteger;
public class MultipleTbQueueCallbackWrapper implements TbQueueCallback {
private final AtomicInteger tbQueueCallbackCount;
private final TbQueueCallback callback;
public MultipleTbQueueCallbackWrapper(int tbQueueCallbackCount, TbQueueCallback callback) {
this.tbQueueCallbackCount = new AtomicInteger(tbQueueCallbackCount);
this.callback = callback;
}
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
if (tbQueueCallbackCount.decrementAndGet() <= 0) {
callback.onSuccess(metadata);
}
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(new RuleEngineException(t.getMessage()));
}
}

1
common/queue/src/main/proto/queue.proto

@ -186,6 +186,7 @@ message GetEntityProfileRequestMsg {
message GetEntityProfileResponseMsg { message GetEntityProfileResponseMsg {
string entityType = 1; string entityType = 1;
bytes data = 2; bytes data = 2;
bytes apiState = 3;
} }
message EntityUpdateMsg { message EntityUpdateMsg {

9
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
@ -630,7 +631,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
log.warn("[{}] Failed to submit session event", sessionId, e); if (e instanceof TbRateLimitsException) {
log.trace("[{}] Failed to submit session event", sessionId, e);
} else {
log.warn("[{}] Failed to submit session event", sessionId, e);
}
ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE));
ctx.close(); ctx.close();
} }

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -45,7 +45,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce
*/ */
public interface TransportService { public interface TransportService {
GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg); GetEntityProfileResponseMsg getEntityProfile(GetEntityProfileRequestMsg msg);
void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
@ -62,8 +62,6 @@ public interface TransportService {
void process(ProvisionDeviceRequestMsg msg, void process(ProvisionDeviceRequestMsg msg,
TransportServiceCallback<ProvisionDeviceResponseMsg> callback); TransportServiceCallback<ProvisionDeviceResponseMsg> callback);
void onProfileUpdate(DeviceProfile deviceProfile);
boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback);
boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits);

11
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentMap;
@Slf4j @Slf4j
public class DefaultTransportRateLimitService implements TransportRateLimitService { public class DefaultTransportRateLimitService implements TransportRateLimitService {
private final ConcurrentMap<TenantId, Boolean> tenantAllowed = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TransportRateLimit[]> perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, TransportRateLimit[]> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, TransportRateLimit[]> perDeviceLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<DeviceId, TransportRateLimit[]> perDeviceLimits = new ConcurrentHashMap<>();
@ -46,6 +47,9 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
@Override @Override
public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) {
if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) {
return TransportRateLimitType.TENANT_ADDED_TO_DISABLED_LIST;
}
TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId);
TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId);
for (TransportRateLimitType limitType : limits) { for (TransportRateLimitType limitType : limits) {
@ -85,6 +89,11 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
perDeviceLimits.remove(deviceId); perDeviceLimits.remove(deviceId);
} }
@Override
public void update(TenantId tenantId, boolean allowed) {
tenantAllowed.put(tenantId, allowed);
}
private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) {
TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId);
if (oldRateLimits == null) { if (oldRateLimits == null) {

1
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java

@ -31,4 +31,5 @@ public interface TransportRateLimitService {
void remove(DeviceId deviceId); void remove(DeviceId deviceId);
void update(TenantId tenantId, boolean transportEnabled);
} }

3
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -19,6 +19,7 @@ import lombok.Getter;
public enum TransportRateLimitType { public enum TransportRateLimitType {
TENANT_ADDED_TO_DISABLED_LIST("general.tenant.disabled", true, false),
TENANT_MAX_MSGS("transport.tenant.msg", true, true), TENANT_MAX_MSGS("transport.tenant.msg", true, true),
TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true),
TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false),

17
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -237,7 +238,7 @@ public class DefaultTransportService implements TransportService {
} }
@Override @Override
public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { public TransportProtos.GetEntityProfileResponseMsg getEntityProfile(TransportProtos.GetEntityProfileRequestMsg msg) {
TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg = TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg =
new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build());
try { try {
@ -641,8 +642,7 @@ public class DefaultTransportService implements TransportService {
onProfileUpdate(deviceProfile); onProfileUpdate(deviceProfile);
} }
} else if (EntityType.TENANT_PROFILE.equals(entityType)) { } else if (EntityType.TENANT_PROFILE.equals(entityType)) {
TenantProfileUpdateResult update = tenantProfileCache.put(msg.getData()); rateLimitService.update(tenantProfileCache.put(msg.getData()));
rateLimitService.update(update);
} else if (EntityType.TENANT.equals(entityType)) { } else if (EntityType.TENANT.equals(entityType)) {
Optional<Tenant> profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); Optional<Tenant> profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray());
if (profileOpt.isPresent()) { if (profileOpt.isPresent()) {
@ -652,6 +652,12 @@ public class DefaultTransportService implements TransportService {
rateLimitService.update(tenant.getId()); rateLimitService.update(tenant.getId());
} }
} }
} else if (EntityType.API_USAGE_STATE.equals(entityType)) {
Optional<ApiUsageState> stateOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray());
if (stateOpt.isPresent()) {
ApiUsageState apiUsageState = stateOpt.get();
rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled());
}
} }
} else if (toSessionMsg.hasEntityDeleteMsg()) { } else if (toSessionMsg.hasEntityDeleteMsg()) {
TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg();
@ -673,8 +679,7 @@ public class DefaultTransportService implements TransportService {
} }
} }
@Override private void onProfileUpdate(DeviceProfile deviceProfile) {
public void onProfileUpdate(DeviceProfile deviceProfile) {
long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits();
long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits();
sessions.forEach((id, md) -> { sessions.forEach((id, md) -> {

26
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -18,21 +18,19 @@ package org.thingsboard.server.common.transport.service;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.limits.TransportRateLimitService;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
import org.thingsboard.server.queue.util.TbTransportComponent; import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.Collections; import java.util.Collections;
@ -54,8 +52,15 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil
private final ConcurrentMap<TenantProfileId, Set<TenantId>> tenantProfileIds = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantProfileId, Set<TenantId>> tenantProfileIds = new ConcurrentHashMap<>();
private final DataDecodingEncodingService dataDecodingEncodingService; private final DataDecodingEncodingService dataDecodingEncodingService;
private TransportRateLimitService rateLimitService;
private TransportService transportService; private TransportService transportService;
@Lazy
@Autowired
public void setRateLimitService(TransportRateLimitService rateLimitService) {
this.rateLimitService = rateLimitService;
}
@Lazy @Lazy
@Autowired @Autowired
public void setTransportService(TransportService transportService) { public void setTransportService(TransportService transportService) {
@ -77,7 +82,8 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil
if (profileOpt.isPresent()) { if (profileOpt.isPresent()) {
TenantProfile newProfile = profileOpt.get(); TenantProfile newProfile = profileOpt.get();
log.trace("[{}] put: {}", newProfile.getId(), newProfile); log.trace("[{}] put: {}", newProfile.getId(), newProfile);
return new TenantProfileUpdateResult(newProfile, tenantProfileIds.get(newProfile.getId())); Set<TenantId> affectedTenants = tenantProfileIds.get(newProfile.getId());
return new TenantProfileUpdateResult(newProfile, affectedTenants != null ? affectedTenants : Collections.emptySet());
} else { } else {
log.warn("Failed to decode profile: {}", profileBody.toString()); log.warn("Failed to decode profile: {}", profileBody.toString());
return new TenantProfileUpdateResult(null, Collections.emptySet()); return new TenantProfileUpdateResult(null, Collections.emptySet());
@ -127,8 +133,8 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil
.setEntityIdMSB(tenantId.getId().getMostSignificantBits()) .setEntityIdMSB(tenantId.getId().getMostSignificantBits())
.setEntityIdLSB(tenantId.getId().getLeastSignificantBits()) .setEntityIdLSB(tenantId.getId().getLeastSignificantBits())
.build(); .build();
TransportProtos.GetEntityProfileResponseMsg routingInfo = transportService.getRoutingInfo(msg); TransportProtos.GetEntityProfileResponseMsg entityProfileMsg = transportService.getEntityProfile(msg);
Optional<TenantProfile> profileOpt = dataDecodingEncodingService.decode(routingInfo.getData().toByteArray()); Optional<TenantProfile> profileOpt = dataDecodingEncodingService.decode(entityProfileMsg.getData().toByteArray());
if (profileOpt.isPresent()) { if (profileOpt.isPresent()) {
profile = profileOpt.get(); profile = profileOpt.get();
TenantProfile existingProfile = profiles.get(profile.getId()); TenantProfile existingProfile = profiles.get(profile.getId());
@ -140,9 +146,11 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil
tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId);
tenantIds.put(tenantId, profile.getId()); tenantIds.put(tenantId, profile.getId());
} else { } else {
log.warn("[{}] Can't decode tenant profile: {}", tenantId, routingInfo.getData()); log.warn("[{}] Can't decode tenant profile: {}", tenantId, entityProfileMsg.getData());
throw new RuntimeException("Can't decode tenant profile!"); throw new RuntimeException("Can't decode tenant profile!");
} }
Optional<ApiUsageState> apiStateOpt = dataDecodingEncodingService.decode(entityProfileMsg.getApiState().toByteArray());
apiStateOpt.ifPresent(apiUsageState -> rateLimitService.update(tenantId, apiUsageState.isTransportEnabled()));
} }
} finally { } finally {
tenantProfileFetchLock.unlock(); tenantProfileFetchLock.unlock();

4
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -445,6 +445,10 @@ public class ModelConstants {
public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY;
public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN;
public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN;
public static final String API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN = "transport_enabled";
public static final String API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN = "db_storage_enabled";
public static final String API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN = "re_exec_enabled";
public static final String API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN = "js_exec_enabled";
/** /**
* Cassandra attributes and timeseries constants. * Cassandra attributes and timeseries constants.

19
dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java

@ -17,6 +17,8 @@ package org.thingsboard.server.dao.model.sql;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -51,6 +53,15 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
@Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN)
private UUID entityId; private UUID entityId;
@Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN)
private boolean transportEnabled = true;
@Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN)
private boolean dbStorageEnabled = true;
@Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN)
private boolean reExecEnabled = true;
@Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN)
private boolean jsExecEnabled = true;
public ApiUsageStateEntity() { public ApiUsageStateEntity() {
} }
@ -66,6 +77,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
this.entityType = ur.getEntityId().getEntityType().name(); this.entityType = ur.getEntityId().getEntityType().name();
this.entityId = ur.getEntityId().getId(); this.entityId = ur.getEntityId().getId();
} }
this.transportEnabled = ur.isTransportEnabled();
this.dbStorageEnabled = ur.isDbStorageEnabled();
this.reExecEnabled = ur.isReExecEnabled();
this.jsExecEnabled = ur.isJsExecEnabled();
} }
@Override @Override
@ -78,6 +93,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
if (entityId != null) { if (entityId != null) {
ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
} }
ur.setTransportEnabled(transportEnabled);
ur.setDbStorageEnabled(dbStorageEnabled);
ur.setReExecEnabled(reExecEnabled);
ur.setJsExecEnabled(jsExecEnabled);
return ur; return ur;
} }

4
dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java → dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java

@ -31,13 +31,13 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service @Service
@Slf4j @Slf4j
public class ApiApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private final ApiUsageStateDao apiUsageStateDao; private final ApiUsageStateDao apiUsageStateDao;
private final TenantDao tenantDao; private final TenantDao tenantDao;
public ApiApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) {
this.tenantDao = tenantDao; this.tenantDao = tenantDao;
this.apiUsageStateDao = apiUsageStateDao; this.apiUsageStateDao = apiUsageStateDao;
} }

4
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -411,5 +411,9 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
tenant_id uuid, tenant_id uuid,
entity_type varchar(32), entity_type varchar(32),
entity_id uuid, entity_id uuid,
transport_enabled boolean,
db_storage_enabled boolean,
re_exec_enabled boolean,
js_exec_enabled boolean,
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
); );

4
dao/src/main/resources/sql/schema-entities.sql

@ -437,6 +437,10 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
tenant_id uuid, tenant_id uuid,
entity_type varchar(32), entity_type varchar(32),
entity_id uuid, entity_id uuid,
transport_enabled boolean,
db_storage_enabled boolean,
re_exec_enabled boolean,
js_exec_enabled boolean,
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
); );

13
dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java

@ -48,4 +48,17 @@ public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest {
Assert.assertNotNull(apiUsageState); Assert.assertNotNull(apiUsageState);
} }
@Test
public void testUpdateApiUsageState(){
ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNotNull(apiUsageState);
Assert.assertTrue(apiUsageState.isTransportEnabled());
apiUsageState.setTransportEnabled(false);
apiUsageState = apiUsageStateService.update(apiUsageState);
Assert.assertNotNull(apiUsageState);
apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNotNull(apiUsageState);
Assert.assertFalse(apiUsageState.isTransportEnabled());
}
} }

Loading…
Cancel
Save