Browse Source

JS Invoke Service stats

pull/3688/head
Andrii Shvaika 6 years ago
parent
commit
b38b47f409
  1. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  2. 2
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  3. 15
      application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java
  4. 36
      application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  6. 11
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  7. 13
      application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java
  8. 11
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java
  10. 5
      application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java
  11. 5
      application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java
  12. 5
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  13. 11
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  15. 250
      application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
  16. 57
      application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java
  17. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java
  18. 13
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java
  19. 2
      common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java
  20. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  21. 5
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  22. 47
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java
  23. 128
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java
  24. 11
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java
  25. 5
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java
  26. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java
  27. 48
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java
  28. 32
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  29. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java
  30. 5
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
  31. 5
      dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java

2
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -312,7 +312,7 @@ class DefaultTbContext implements TbContext {
@Override @Override
public ScriptEngine createJsScriptEngine(String script, String... argNames) { public ScriptEngine createJsScriptEngine(String script, String... argNames) {
return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames);
} }
@Override @Override

2
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -347,7 +347,7 @@ public class RuleChainController extends BaseController {
String errorText = ""; String errorText = "";
ScriptEngine engine = null; ScriptEngine engine = null;
try { try {
engine = new RuleNodeJsScriptEngine(jsInvokeService, getCurrentUser().getId(), script, argNames); engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, getCurrentUser().getId(), script, argNames);
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
switch (scriptType) { switch (scriptType) {
case "update": case "update":

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

@ -1,3 +1,18 @@
/**
* 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.service.apiusage; package org.thingsboard.server.service.apiusage;
public enum ApiFeature { public enum ApiFeature {

36
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,
@ -21,6 +21,7 @@ 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.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.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.TenantId;
@ -100,15 +101,18 @@ public class TenantApiUsageState {
} }
public long getProfileThreshold(ApiUsageRecordKey key) { public long getProfileThreshold(ApiUsageRecordKey key) {
Object threshold = tenantProfileData.getProperties().get(key.name()); DefaultTenantProfileConfiguration config = (DefaultTenantProfileConfiguration) tenantProfileData.getConfiguration();
if (threshold != null) { switch (key) {
if (threshold instanceof String) { case TRANSPORT_MSG_COUNT:
return Long.parseLong((String) threshold); return config.getMaxTransportMessages();
} else if (threshold instanceof Long) { case TRANSPORT_DP_COUNT:
return (Long) threshold; return config.getMaxTransportDataPoints();
} else if (threshold instanceof Integer) { case JS_EXEC_COUNT:
return (Integer) threshold; return config.getMaxJSExecutions();
} case RE_EXEC_COUNT:
return config.getMaxREExecutions();
case STORAGE_DP_COUNT:
return config.getMaxDPStorageDays();
} }
return 0L; return 0L;
} }
@ -155,12 +159,12 @@ public class TenantApiUsageState {
public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) {
switch (recordKey) { switch (recordKey) {
case MSG_COUNT: case TRANSPORT_MSG_COUNT:
case DP_TRANSPORT_COUNT: case TRANSPORT_DP_COUNT:
return isTransportEnabled(); return isTransportEnabled();
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
return isRuleEngineEnabled(); return isRuleEngineEnabled();
case DP_STORAGE_COUNT: case STORAGE_DP_COUNT:
return isDbStorageEnabled(); return isDbStorageEnabled();
case JS_EXEC_COUNT: case JS_EXEC_COUNT:
return isJsExecEnabled(); return isJsExecEnabled();
@ -173,8 +177,8 @@ public class TenantApiUsageState {
ApiFeature feature = null; ApiFeature feature = null;
boolean currentValue = isFeatureEnabled(recordKey); boolean currentValue = isFeatureEnabled(recordKey);
switch (recordKey) { switch (recordKey) {
case MSG_COUNT: case TRANSPORT_MSG_COUNT:
case DP_TRANSPORT_COUNT: case TRANSPORT_DP_COUNT:
feature = ApiFeature.TRANSPORT; feature = ApiFeature.TRANSPORT;
setTransportEnabled(value); setTransportEnabled(value);
break; break;
@ -182,7 +186,7 @@ public class TenantApiUsageState {
feature = ApiFeature.RE; feature = ApiFeature.RE;
setRuleEngineEnabled(value); setRuleEngineEnabled(value);
break; break;
case DP_STORAGE_COUNT: case STORAGE_DP_COUNT:
feature = ApiFeature.DB; feature = ApiFeature.DB;
setDbStorageEnabled(value); setDbStorageEnabled(value);
break; break;

2
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,

11
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,
@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
@ -153,10 +154,14 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId()); TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId());
tenantProfileCache.evict(tenantProfileId); tenantProfileCache.evict(tenantProfileId);
apiUsageStateService.onTenantProfileUpdate(tenantProfileId); if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
apiUsageStateService.onTenantProfileUpdate(tenantProfileId);
}
} else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
tenantProfileCache.evict(componentLifecycleMsg.getTenantId()); tenantProfileCache.evict(componentLifecycleMsg.getTenantId());
apiUsageStateService.onTenantUpdate(componentLifecycleMsg.getTenantId()); if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
apiUsageStateService.onTenantUpdate(componentLifecycleMsg.getTenantId());
}
} else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
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())) {

13
application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java

@ -19,6 +19,9 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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.id.TenantId;
import org.thingsboard.server.queue.usagestats.TbUsageStatsClient;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -33,10 +36,15 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public abstract class AbstractJsInvokeService implements JsInvokeService { public abstract class AbstractJsInvokeService implements JsInvokeService {
private final TbUsageStatsClient apiUsageStatsClient;
protected ScheduledExecutorService timeoutExecutorService; protected ScheduledExecutorService timeoutExecutorService;
protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>(); protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>();
protected Map<UUID, BlackListInfo> blackListedFunctions = new ConcurrentHashMap<>(); protected Map<UUID, BlackListInfo> blackListedFunctions = new ConcurrentHashMap<>();
protected AbstractJsInvokeService(TbUsageStatsClient apiUsageStatsClient) {
this.apiUsageStatsClient = apiUsageStatsClient;
}
public void init(long maxRequestsTimeout) { public void init(long maxRequestsTimeout) {
if (maxRequestsTimeout > 0) { if (maxRequestsTimeout > 0) {
timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout")); timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout"));
@ -50,7 +58,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService {
} }
@Override @Override
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) { public ListenableFuture<UUID> eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) {
UUID scriptId = UUID.randomUUID(); UUID scriptId = UUID.randomUUID();
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
@ -58,12 +66,13 @@ public abstract class AbstractJsInvokeService implements JsInvokeService {
} }
@Override @Override
public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) { public ListenableFuture<Object> invokeFunction(TenantId tenantId, UUID scriptId, Object... args) {
String functionName = scriptIdToNameMap.get(scriptId); String functionName = scriptIdToNameMap.get(scriptId);
if (functionName == null) { if (functionName == null) {
return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
} }
if (!isBlackListed(scriptId)) { if (!isBlackListed(scriptId)) {
apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1);
return doInvokeFunction(scriptId, functionName, args); return doInvokeFunction(scriptId, functionName, args);
} else { } else {
return Futures.immediateFailedFuture( return Futures.immediateFailedFuture(

11
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.queue.usagestats.TbUsageStatsClient;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -38,7 +38,6 @@ import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -57,9 +56,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
private final FutureCallback<UUID> evalCallback = new JsStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); private final FutureCallback<UUID> evalCallback = new JsStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs);
private final FutureCallback<Object> invokeCallback = new JsStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); private final FutureCallback<Object> invokeCallback = new JsStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs);
@Autowired
@Getter @Getter
private JsExecutorService jsExecutor; private final JsExecutorService jsExecutor;
@Value("${js.local.max_requests_timeout:0}") @Value("${js.local.max_requests_timeout:0}")
private long maxRequestsTimeout; private long maxRequestsTimeout;
@ -67,6 +65,11 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
@Value("${js.local.stats.enabled:false}") @Value("${js.local.stats.enabled:false}")
private boolean statsEnabled; private boolean statsEnabled;
public AbstractNashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) {
super(apiUsageStatsClient);
this.jsExecutor = jsExecutor;
}
@Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}")
public void printStats() { public void printStats() {
if (statsEnabled) { if (statsEnabled) {

2
application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java

@ -27,7 +27,7 @@ public class JsExecutorService extends AbstractListeningExecutor {
@Override @Override
protected int getThreadPollSize() { protected int getThreadPollSize() {
return jsExecutorThreadPoolSize; return Math.max(jsExecutorThreadPoolSize, 1);
} }
} }

5
application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java

@ -17,14 +17,15 @@ package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
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 java.util.UUID; import java.util.UUID;
public interface JsInvokeService { public interface JsInvokeService {
ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames); ListenableFuture<UUID> eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames);
ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args); ListenableFuture<Object> invokeFunction(TenantId tenantId, UUID scriptId, Object... args);
ListenableFuture<Void> release(UUID scriptId); ListenableFuture<Void> release(UUID scriptId);

5
application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.queue.usagestats.TbUsageStatsClient;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -42,6 +43,10 @@ public class NashornJsInvokeService extends AbstractNashornJsInvokeService {
@Value("${js.local.max_black_list_duration_sec:60}") @Value("${js.local.max_black_list_duration_sec:60}")
private int maxBlackListDurationSec; private int maxBlackListDurationSec;
public NashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) {
super(apiUsageStatsClient, jsExecutor);
}
@Override @Override
protected boolean useJsSandbox() { protected boolean useJsSandbox() {
return useJsSandbox; return useJsSandbox;

5
application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java

@ -30,6 +30,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.usagestats.TbUsageStatsClient;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -68,6 +69,10 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
public RemoteJsInvokeService(TbUsageStatsClient apiUsageStatsClient) {
super(apiUsageStatsClient);
}
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}")
public void printStats() { public void printStats() {
if (statsEnabled) { if (statsEnabled) {

11
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -43,13 +44,15 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
private final JsInvokeService sandboxService; private final JsInvokeService sandboxService;
private final UUID scriptId; private final UUID scriptId;
private final TenantId tenantId;
private final EntityId entityId; private final EntityId entityId;
public RuleNodeJsScriptEngine(JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) { public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) {
this.tenantId = tenantId;
this.sandboxService = sandboxService; this.sandboxService = sandboxService;
this.entityId = entityId; this.entityId = entityId;
try { try {
this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); this.scriptId = this.sandboxService.eval(tenantId, JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
} catch (Exception e) { } catch (Exception e) {
Throwable t = e; Throwable t = e;
if (e instanceof ExecutionException) { if (e instanceof ExecutionException) {
@ -203,7 +206,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
private JsonNode executeScript(TbMsg msg) throws ScriptException { private JsonNode executeScript(TbMsg msg) throws ScriptException {
try { try {
String[] inArgs = prepareArgs(msg); String[] inArgs = prepareArgs(msg);
String eval = sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); String eval = sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
return mapper.readTree(eval); return mapper.readTree(eval);
} catch (ExecutionException e) { } catch (ExecutionException e) {
if (e.getCause() instanceof ScriptException) { if (e.getCause() instanceof ScriptException) {
@ -220,7 +223,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) {
String[] inArgs = prepareArgs(msg); String[] inArgs = prepareArgs(msg);
return Futures.transformAsync(sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]), return Futures.transformAsync(sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]),
o -> { o -> {
try { try {
return Futures.immediateFuture(mapper.readTree(o.toString())); return Futures.immediateFuture(mapper.readTree(o.toString()));

2
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,

250
application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java

@ -1,250 +0,0 @@
/**
* 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.service.script;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.collect.Sets;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
public class RuleNodeJsScriptEngineTest {
private ScriptEngine scriptEngine;
private TestNashornJsInvokeService jsSandboxService;
private EntityId ruleNodeId = new RuleNodeId(Uuids.timeBased());
@Before
public void beforeTest() throws Exception {
jsSandboxService = new TestNashornJsInvokeService(false, 1, 100, 3);
}
@After
public void afterTest() throws Exception {
jsSandboxService.stop();
}
@Test
public void msgCanBeUpdated() throws ScriptException {
String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson);
TbMsg actual = scriptEngine.executeUpdate(msg);
assertEquals("70", actual.getMetaData().getValue("temp"));
scriptEngine.destroy();
}
@Test
public void newAttributesCanBeAddedInMsg() throws ScriptException {
String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson);
TbMsg actual = scriptEngine.executeUpdate(msg);
assertEquals("94", actual.getMetaData().getValue("newAttr"));
scriptEngine.destroy();
}
@Test
public void payloadCanBeUpdated() throws ScriptException {
String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}";
TbMsg msg =TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, rawJson);
TbMsg actual = scriptEngine.executeUpdate(msg);
String expectedJson = "{\"name\":\"Vit\",\"passed\":35,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}";
assertEquals(expectedJson, actual.getData());
scriptEngine.destroy();
}
@Test
public void metadataAccessibleForFilter() throws ScriptException {
String function = "return metadata.humidity < 15;";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, rawJson);
assertFalse(scriptEngine.executeFilter(msg));
scriptEngine.destroy();
}
@Test
public void dataAccessibleForFilter() throws ScriptException {
String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = TbMsg.newMsg( "USER", null, metaData,TbMsgDataType.JSON, rawJson);
assertTrue(scriptEngine.executeFilter(msg));
scriptEngine.destroy();
}
@Test
public void dataAccessibleForSwitch() throws ScriptException {
String jsCode = "function nextRelation(metadata, msg) {\n" +
" if(msg.passed == 5 && metadata.temp == 10)\n" +
" return 'one'\n" +
" else\n" +
" return 'two';\n" +
"};\n" +
"\n" +
"return nextRelation(metadata, msg);";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson);
Set<String> actual = scriptEngine.executeSwitch(msg);
assertEquals(Sets.newHashSet("one"), actual);
scriptEngine.destroy();
}
@Test
public void multipleRelationsReturnedFromSwitch() throws ScriptException {
String jsCode = "function nextRelation(metadata, msg) {\n" +
" if(msg.passed == 5 && metadata.temp == 10)\n" +
" return ['three', 'one']\n" +
" else\n" +
" return 'two';\n" +
"};\n" +
"\n" +
"return nextRelation(metadata, msg);";
scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson);
Set<String> actual = scriptEngine.executeSwitch(msg);
assertEquals(Sets.newHashSet("one", "three"), actual);
scriptEngine.destroy();
}
@Test
public void concurrentReleasedCorrectly() throws InterruptedException, ExecutionException {
String code = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
int repeat = 1000;
ExecutorService service = Executors.newFixedThreadPool(repeat);
Map<UUID, Object> scriptIds = new ConcurrentHashMap<>();
CountDownLatch startLatch = new CountDownLatch(repeat);
CountDownLatch finishLatch = new CountDownLatch(repeat);
AtomicInteger failedCount = new AtomicInteger(0);
for (int i = 0; i < repeat; i++) {
service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code));
}
finishLatch.await();
assertTrue(scriptIds.size() == 1);
assertTrue(failedCount.get() == 0);
CountDownLatch nextStart = new CountDownLatch(repeat);
CountDownLatch nextFinish = new CountDownLatch(repeat);
for (int i = 0; i < repeat; i++) {
service.submit(() -> runScript(nextStart, nextFinish, failedCount, scriptIds, code));
}
nextFinish.await();
assertTrue(scriptIds.size() == 1);
assertTrue(failedCount.get() == 0);
service.shutdownNow();
}
@Test
public void concurrentFailedEvaluationShouldThrowException() throws InterruptedException {
String code = "metadata.temp = metadata.temp * 10; urn {metadata: metadata};";
int repeat = 10000;
ExecutorService service = Executors.newFixedThreadPool(repeat);
Map<UUID, Object> scriptIds = new ConcurrentHashMap<>();
CountDownLatch startLatch = new CountDownLatch(repeat);
CountDownLatch finishLatch = new CountDownLatch(repeat);
AtomicInteger failedCount = new AtomicInteger(0);
for (int i = 0; i < repeat; i++) {
service.submit(() -> {
service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code));
});
}
finishLatch.await();
assertTrue(scriptIds.isEmpty());
assertEquals(repeat, failedCount.get());
service.shutdownNow();
}
private void runScript(CountDownLatch startLatch, CountDownLatch finishLatch, AtomicInteger failedCount,
Map<UUID, Object> scriptIds, String code) {
try {
for (int k = 0; k < 10; k++) {
startLatch.countDown();
startLatch.await();
UUID scriptId = jsSandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, code).get();
scriptIds.put(scriptId, new Object());
jsSandboxService.invokeFunction(scriptId, "{}", "{}", "TEXT").get();
jsSandboxService.release(scriptId).get();
}
} catch (Throwable th) {
failedCount.incrementAndGet();
} finally {
finishLatch.countDown();
}
}
}

57
application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java

@ -1,57 +0,0 @@
/**
* 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.service.script;
public class TestNashornJsInvokeService extends AbstractNashornJsInvokeService {
private boolean useJsSandbox;
private final int monitorThreadPoolSize;
private final long maxCpuTime;
private final int maxErrors;
public TestNashornJsInvokeService(boolean useJsSandbox, int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
this.useJsSandbox = useJsSandbox;
this.monitorThreadPoolSize = monitorThreadPoolSize;
this.maxCpuTime = maxCpuTime;
this.maxErrors = maxErrors;
init();
}
@Override
protected boolean useJsSandbox() {
return useJsSandbox;
}
@Override
protected int getMonitorThreadPoolSize() {
return monitorThreadPoolSize;
}
@Override
protected long getMaxCpuTime() {
return maxCpuTime;
}
@Override
protected int getMaxErrors() {
return maxErrors;
}
@Override
protected long getMaxBlacklistDuration() {
return 100000;
}
}

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

@ -17,9 +17,9 @@ package org.thingsboard.server.common.data;
public enum ApiUsageRecordKey { public enum ApiUsageRecordKey {
MSG_COUNT, TRANSPORT_MSG_COUNT,
DP_TRANSPORT_COUNT, TRANSPORT_DP_COUNT,
DP_STORAGE_COUNT, STORAGE_DP_COUNT,
RE_EXEC_COUNT, RE_EXEC_COUNT,
JS_EXEC_COUNT JS_EXEC_COUNT

13
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java

@ -28,17 +28,4 @@ public class TenantProfileData {
private TenantProfileConfiguration configuration; private TenantProfileConfiguration configuration;
@JsonIgnore
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return this.properties;
}
@JsonAnySetter
public void put(String name, Object value) {
this.properties.put(name, value);
}
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.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,

2
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,

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

@ -19,7 +19,6 @@ 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.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.limits.TransportRateLimitType;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
@ -62,10 +61,6 @@ public interface TransportService {
void process(ProvisionDeviceRequestMsg msg, void process(ProvisionDeviceRequestMsg msg,
TransportServiceCallback<ProvisionDeviceResponseMsg> callback); TransportServiceCallback<ProvisionDeviceResponseMsg> callback);
boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback);
boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits);
void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback);

47
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java

@ -1,47 +0,0 @@
/**
* 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.common.transport.limits;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
@Slf4j
@Component
public class DefaultTransportRateLimitFactory implements TransportRateLimitFactory {
private static final DummyTransportRateLimit ALWAYS_TRUE = new DummyTransportRateLimit();
@Override
public TransportRateLimit create(TransportRateLimitType type, Object configuration) {
if (!StringUtils.isEmpty(configuration)) {
try {
return new SimpleTransportRateLimit(new TbRateLimits(configuration.toString()), configuration.toString());
} catch (Exception e) {
log.warn("[{}] Failed to init rate limit with configuration: {}", type, configuration, e);
return ALWAYS_TRUE;
}
} else {
return ALWAYS_TRUE;
}
}
@Override
public TransportRateLimit createDefault(TransportRateLimitType type) {
return ALWAYS_TRUE;
}
}

128
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,
@ -17,76 +17,96 @@ package org.thingsboard.server.common.transport.limits;
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.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.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
import org.thingsboard.server.queue.util.TbTransportComponent; import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
@Service @Service
@TbTransportComponent @TbTransportComponent
@Slf4j @Slf4j
public class DefaultTransportRateLimitService implements TransportRateLimitService { public class DefaultTransportRateLimitService implements TransportRateLimitService {
private final static DummyTransportRateLimit ALLOW = new DummyTransportRateLimit();
private final ConcurrentMap<TenantId, Boolean> tenantAllowed = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, Boolean> tenantAllowed = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TransportRateLimit[]> perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, Set<DeviceId>> tenantDevices = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, TransportRateLimit[]> perDeviceLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, EntityTransportRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, EntityTransportRateLimits> perDeviceLimits = new ConcurrentHashMap<>();
private final TransportRateLimitFactory rateLimitFactory;
private final TransportTenantProfileCache tenantProfileCache; private final TransportTenantProfileCache tenantProfileCache;
public DefaultTransportRateLimitService(TransportRateLimitFactory rateLimitFactory, TransportTenantProfileCache tenantProfileCache) { public DefaultTransportRateLimitService(TransportTenantProfileCache tenantProfileCache) {
this.rateLimitFactory = rateLimitFactory;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
} }
@Override @Override
public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { public EntityType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints) {
if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) { if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) {
return TransportRateLimitType.TENANT_ADDED_TO_DISABLED_LIST; return EntityType.TENANT;
} }
TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); if (!checkEntityRateLimit(dataPoints, getTenantRateLimits(tenantId))) {
TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); return EntityType.TENANT;
for (TransportRateLimitType limitType : limits) { }
TransportRateLimit rateLimit; if (!checkEntityRateLimit(dataPoints, getDeviceRateLimits(tenantId, deviceId))) {
if (limitType.isTenantLevel()) { return EntityType.DEVICE;
rateLimit = tenantLimits[limitType.ordinal()];
} else {
rateLimit = deviceLimits[limitType.ordinal()];
}
if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) {
return limitType;
}
} }
return null; return null;
} }
private boolean checkEntityRateLimit(int dataPoints, EntityTransportRateLimits tenantLimits) {
if (dataPoints > 0) {
return tenantLimits.getTelemetryMsgRateLimit().tryConsume() && tenantLimits.getTelemetryDataPointsRateLimit().tryConsume(dataPoints);
} else {
return tenantLimits.getRegularMsgRateLimit().tryConsume();
}
}
@Override @Override
public void update(TenantProfileUpdateResult update) { public void update(TenantProfileUpdateResult update) {
TransportRateLimit[] newLimits = createTransportRateLimits(update.getProfile()); EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true);
EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false);
for (TenantId tenantId : update.getAffectedTenants()) { for (TenantId tenantId : update.getAffectedTenants()) {
mergeLimits(tenantId, newLimits); mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put);
tenantDevices.get(tenantId).forEach(deviceId -> {
mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put);
});
} }
} }
@Override @Override
public void update(TenantId tenantId) { public void update(TenantId tenantId) {
mergeLimits(tenantId, fetchProfileAndInit(tenantId)); EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), true);
EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), false);
mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put);
tenantDevices.get(tenantId).forEach(deviceId -> {
mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put);
});
} }
@Override @Override
public void remove(TenantId tenantId) { public void remove(TenantId tenantId) {
perTenantLimits.remove(tenantId); perTenantLimits.remove(tenantId);
tenantDevices.remove(tenantId);
} }
@Override @Override
public void remove(DeviceId deviceId) { public void remove(DeviceId deviceId) {
perDeviceLimits.remove(deviceId); perDeviceLimits.remove(deviceId);
tenantDevices.values().forEach(set -> set.remove(deviceId));
} }
@Override @Override
@ -94,48 +114,66 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
tenantAllowed.put(tenantId, allowed); tenantAllowed.put(tenantId, allowed);
} }
private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { private <T> void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits,
TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); Function<T, EntityTransportRateLimits> getFunction,
BiConsumer<T, EntityTransportRateLimits> putFunction) {
EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId);
if (oldRateLimits == null) { if (oldRateLimits == null) {
perTenantLimits.put(tenantId, newRateLimits); putFunction.accept(deviceId, newRateLimits);
} else { } else {
for (int i = 0; i < TransportRateLimitType.values().length; i++) { EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits);
TransportRateLimit newLimit = newRateLimits[i]; if (updated != null) {
TransportRateLimit oldLimit = oldRateLimits[i]; putFunction.accept(deviceId, updated);
if (newLimit != null && (oldLimit == null || !oldLimit.getConfiguration().equals(newLimit.getConfiguration()))) {
oldRateLimits[i] = newLimit;
}
} }
} }
} }
private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { private EntityTransportRateLimits merge(EntityTransportRateLimits oldRateLimits, EntityTransportRateLimits newRateLimits) {
return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); boolean regularUpdate = !oldRateLimits.getRegularMsgRateLimit().getConfiguration().equals(newRateLimits.getRegularMsgRateLimit().getConfiguration());
boolean telemetryMsgRateUpdate = !oldRateLimits.getTelemetryMsgRateLimit().getConfiguration().equals(newRateLimits.getTelemetryMsgRateLimit().getConfiguration());
boolean telemetryDataPointUpdate = !oldRateLimits.getTelemetryDataPointsRateLimit().getConfiguration().equals(newRateLimits.getTelemetryDataPointsRateLimit().getConfiguration());
if (regularUpdate || telemetryMsgRateUpdate || telemetryDataPointUpdate) {
return new EntityTransportRateLimits(
regularUpdate ? newLimit(newRateLimits.getRegularMsgRateLimit().getConfiguration()) : oldRateLimits.getRegularMsgRateLimit(),
telemetryMsgRateUpdate ? newLimit(newRateLimits.getTelemetryMsgRateLimit().getConfiguration()) : oldRateLimits.getTelemetryMsgRateLimit(),
telemetryDataPointUpdate ? newLimit(newRateLimits.getTelemetryDataPointsRateLimit().getConfiguration()) : oldRateLimits.getTelemetryDataPointsRateLimit());
} else {
return null;
}
} }
private TransportRateLimit[] createTransportRateLimits(TenantProfile tenantProfile) { private EntityTransportRateLimits createRateLimits(TenantProfile tenantProfile, boolean tenant) {
TenantProfileData profileData = tenantProfile.getProfileData(); TenantProfileData profileData = tenantProfile.getProfileData();
TransportRateLimit[] rateLimits = new TransportRateLimit[TransportRateLimitType.values().length]; DefaultTenantProfileConfiguration profile = (DefaultTenantProfileConfiguration) profileData.getConfiguration();
for (TransportRateLimitType type : TransportRateLimitType.values()) { if (profile == null) {
rateLimits[type.ordinal()] = rateLimitFactory.create(type, profileData.getProperties().get(type.getConfigurationKey())); return new EntityTransportRateLimits(ALLOW, ALLOW, ALLOW);
} else {
TransportRateLimit regularMsgRateLimit = newLimit(tenant ? profile.getTransportTenantMsgRateLimit() : profile.getTransportDeviceMsgRateLimit());
TransportRateLimit telemetryMsgRateLimit = newLimit(tenant ? profile.getTransportTenantTelemetryMsgRateLimit() : profile.getTransportDeviceTelemetryMsgRateLimit());
TransportRateLimit telemetryDpRateLimit = newLimit(tenant ? profile.getTransportTenantTelemetryDataPointsRateLimit() : profile.getTransportTenantTelemetryDataPointsRateLimit());
return new EntityTransportRateLimits(regularMsgRateLimit, telemetryMsgRateLimit, telemetryDpRateLimit);
} }
return rateLimits;
} }
private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) { private static TransportRateLimit newLimit(String config) {
TransportRateLimit[] limits = perTenantLimits.get(tenantId); return StringUtils.isEmpty(config) ? ALLOW : new SimpleTransportRateLimit(config);
}
private EntityTransportRateLimits getTenantRateLimits(TenantId tenantId) {
EntityTransportRateLimits limits = perTenantLimits.get(tenantId);
if (limits == null) { if (limits == null) {
limits = fetchProfileAndInit(tenantId); limits = createRateLimits(tenantProfileCache.get(tenantId), true);
perTenantLimits.put(tenantId, limits); perTenantLimits.put(tenantId, limits);
} }
return limits; return limits;
} }
private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { private EntityTransportRateLimits getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) {
TransportRateLimit[] limits = perDeviceLimits.get(deviceId); EntityTransportRateLimits limits = perDeviceLimits.get(deviceId);
if (limits == null) { if (limits == null) {
limits = fetchProfileAndInit(tenantId); limits = createRateLimits(tenantProfileCache.get(tenantId), false);
perDeviceLimits.put(deviceId, limits); perDeviceLimits.put(deviceId, limits);
tenantDevices.computeIfAbsent(tenantId, id -> ConcurrentHashMap.newKeySet()).add(deviceId);
} }
return limits; return limits;
} }

11
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java → common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java

@ -15,10 +15,15 @@
*/ */
package org.thingsboard.server.common.transport.limits; package org.thingsboard.server.common.transport.limits;
public interface TransportRateLimitFactory { import lombok.AllArgsConstructor;
import lombok.Data;
TransportRateLimit create(TransportRateLimitType type, Object config); @Data
@AllArgsConstructor
public class EntityTransportRateLimits {
TransportRateLimit createDefault(TransportRateLimitType type); private TransportRateLimit regularMsgRateLimit;
private TransportRateLimit telemetryMsgRateLimit;
private TransportRateLimit telemetryDataPointsRateLimit;
} }

5
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java

@ -26,6 +26,11 @@ public class SimpleTransportRateLimit implements TransportRateLimit {
@Getter @Getter
private final String configuration; private final String configuration;
public SimpleTransportRateLimit(String configuration) {
this.configuration = configuration;
this.rateLimit = new TbRateLimits(configuration);
}
@Override @Override
public boolean tryConsume() { public boolean tryConsume() {
return rateLimit.tryConsume(); return rateLimit.tryConsume();

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

@ -15,13 +15,14 @@
*/ */
package org.thingsboard.server.common.transport.limits; package org.thingsboard.server.common.transport.limits;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
public interface TransportRateLimitService { public interface TransportRateLimitService {
TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits); EntityType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints);
void update(TenantProfileUpdateResult update); void update(TenantProfileUpdateResult update);

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

@ -1,48 +0,0 @@
/**
* 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.common.transport.limits;
import lombok.Getter;
public enum TransportRateLimitType {
TENANT_ADDED_TO_DISABLED_LIST("general.tenant.disabled", true, false),
TENANT_MAX_MSGS("transport.tenant.msg", true, true),
TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true),
TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false),
DEVICE_MAX_MSGS("transport.device.msg", false, true),
DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true),
DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false);
@Getter
private final String configurationKey;
@Getter
private final boolean tenantLevel;
@Getter
private final boolean deviceLevel;
@Getter
private final boolean messageLevel;
@Getter
private final boolean dataPointLevel;
TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) {
this.configurationKey = configurationKey;
this.tenantLevel = tenantLevel;
this.deviceLevel = !tenantLevel;
this.messageLevel = messageLevel;
this.dataPointLevel = !messageLevel;
}
}

32
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,
@ -55,8 +55,6 @@ import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGateway
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.limits.TransportRateLimitService;
import org.thingsboard.server.common.transport.limits.TransportRateLimitType;
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.common.transport.util.JsonUtils; import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -364,7 +362,7 @@ public class DefaultTransportService implements TransportService {
for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
dataPoints += tsKv.getKvCount(); dataPoints += tsKv.getKvCount();
} }
if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { if (checkLimits(sessionInfo, msg, callback, dataPoints)) {
reportActivityInternal(sessionInfo); reportActivityInternal(sessionInfo);
TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
@ -385,7 +383,7 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) {
reportActivityInternal(sessionInfo); reportActivityInternal(sessionInfo);
TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
@ -575,31 +573,23 @@ public class DefaultTransportService implements TransportService {
sessions.remove(toSessionId(sessionInfo)); sessions.remove(toSessionId(sessionInfo));
} }
private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS}; private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); return checkLimits(sessionInfo, msg, callback, 0);
@Override
public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
return checkLimits(sessionInfo, msg, callback, 0, DEFAULT);
} }
@Override private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints) {
public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
} }
TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits); EntityType rateLimitedEntityType = rateLimitService.checkLimits(tenantId, deviceId, dataPoints);
if (limit == null) { if (rateLimitedEntityType == null) {
return true; return true;
} else { } else {
if (callback != null) { if (callback != null) {
callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE)); callback.onError(new TbRateLimitsException(rateLimitedEntityType));
}
if (log.isTraceEnabled()) {
log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg);
} }
return false; return false;
} }
@ -831,8 +821,8 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void onSuccess(T msg) { public void onSuccess(T msg) {
try { try {
apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.MSG_COUNT, 1); apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1);
apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.DP_TRANSPORT_COUNT, dataPoints); apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints);
} finally { } finally {
callback.onSuccess(msg); callback.onSuccess(msg);
} }

2
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,

5
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java

@ -29,6 +29,7 @@ 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.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -145,7 +146,9 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
defaultTenantProfile = new TenantProfile(); defaultTenantProfile = new TenantProfile();
defaultTenantProfile.setDefault(true); defaultTenantProfile.setDefault(true);
defaultTenantProfile.setName("Default"); defaultTenantProfile.setName("Default");
defaultTenantProfile.setProfileData(new TenantProfileData()); TenantProfileData profileData = new TenantProfileData();
profileData.setConfiguration(new DefaultTenantProfileConfiguration());
defaultTenantProfile.setProfileData(profileData);
defaultTenantProfile.setDescription("Default tenant profile"); defaultTenantProfile.setDescription("Default tenant profile");
defaultTenantProfile.setIsolatedTbCore(false); defaultTenantProfile.setIsolatedTbCore(false);
defaultTenantProfile.setIsolatedTbRuleEngine(false); defaultTenantProfile.setIsolatedTbRuleEngine(false);

5
dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java

@ -25,6 +25,7 @@ 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.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -262,7 +263,9 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest {
TenantProfile tenantProfile = new TenantProfile(); TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName(name); tenantProfile.setName(name);
tenantProfile.setDescription(name + " Test"); tenantProfile.setDescription(name + " Test");
tenantProfile.setProfileData(new TenantProfileData()); TenantProfileData profileData = new TenantProfileData();
profileData.setConfiguration(new DefaultTenantProfileConfiguration());
tenantProfile.setProfileData(profileData);
tenantProfile.setDefault(false); tenantProfile.setDefault(false);
tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbCore(false);
tenantProfile.setIsolatedTbRuleEngine(false); tenantProfile.setIsolatedTbRuleEngine(false);

Loading…
Cancel
Save