Browse Source

Merge remote-tracking branch 'upstream/master'

pull/1183/head
Viacheslav Kukhtyn 8 years ago
parent
commit
67b8c2f4bf
  1. 2
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
  2. 44
      application/src/main/resources/thingsboard.yml
  3. 12
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  4. 2
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  5. 31
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  6. 1
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java
  7. 10
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  8. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java
  9. 67
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/AbstractQuotaService.java
  10. 45
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/Clock.java
  11. 25
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java
  12. 30
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/RequestLimitPolicy.java
  13. 29
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryCleaner.java
  14. 52
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryLogger.java
  15. 37
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestIntervalRegistry.java
  16. 33
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestLimitPolicy.java
  17. 37
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestsQuotaService.java
  18. 68
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCount.java
  19. 62
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryCleaner.java
  20. 79
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLogger.java
  21. 73
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/KeyBasedIntervalRegistry.java
  22. 31
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java
  23. 54
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java
  24. 33
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java
  25. 32
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java
  26. 30
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java
  27. 66
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java
  28. 47
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestLimitPolicyTest.java
  29. 78
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java
  30. 83
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistryTest.java
  31. 65
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCountTest.java
  32. 63
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLoggerTest.java
  33. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  34. 2
      msa/docker/.env
  35. 1
      msa/docker/docker-compose.yml
  36. 2
      pom.xml
  37. 25
      transport/coap/src/main/resources/tb-coap-transport.yml
  38. 25
      transport/http/src/main/resources/tb-http-transport.yml
  39. 21
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  40. 18
      ui/src/app/api/datasource.service.js
  41. 11
      ui/src/app/components/datakey-config.tpl.html
  42. 2344
      ui/src/app/locale/locale.constant-TR.json
  43. 7
      ui/src/app/locale/locale.constant-en_US.json

2
application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java

@ -50,7 +50,7 @@ public abstract class RuleChainManager extends EntityActorsManager<RuleChainId,
@Override
public void visit(RuleChain entity, ActorRef actorRef) {
if (entity.isRoot()) {
if (entity != null && entity.isRoot()) {
rootChain = entity;
rootChainActor = actorRef;
}

44
application/src/main/resources/thingsboard.yml

@ -82,46 +82,6 @@ dashboard:
# Maximum allowed datapoints fetched by widgets
max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
#Quota parameters
quota:
host:
# Max allowed number of API requests in interval for single host
limit: "${QUOTA_HOST_LIMIT:10000}"
# Interval duration
intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
# Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
# Interval for scheduled task that cleans expired records. TTL is used for expiring
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
# Enable Host API Limits
enabled: "${QUOTA_HOST_ENABLED:false}"
# Array of whitelist hosts
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
# Array of blacklist hosts
blacklist: "${QUOTA_HOST_BLACKLIST:}"
log:
topSize: "${QUOTA_HOST_LOG_TOP_SIZE:10}"
intervalMin: "${QUOTA_HOST_LOG_INTERVAL_MIN:2}"
rule:
tenant:
# Max allowed number of API requests in interval for single tenant
limit: "${QUOTA_TENANT_LIMIT:100000}"
# Interval duration
intervalMs: "${QUOTA_TENANT_INTERVAL_MS:60000}"
# Maximum silence duration for tenant after which Tenant removed from QuotaService. Must be bigger than intervalMs
ttlMs: "${QUOTA_TENANT_TTL_MS:60000}"
# Interval for scheduled task that cleans expired records. TTL is used for expiring
cleanPeriodMs: "${QUOTA_TENANT_CLEAN_PERIOD_MS:300000}"
# Enable Host API Limits
enabled: "${QUOTA_TENANT_ENABLED:true}"
# Array of whitelist tenants
whitelist: "${QUOTA_TENANT_WHITELIST:}"
# Array of blacklist tenants
blacklist: "${QUOTA_HOST_TENANT_BLACKLIST:}"
log:
topSize: "${QUOTA_TENANT_LOG_TOP_SIZE:10}"
intervalMin: "${QUOTA_TENANT_LOG_INTERVAL_MIN:2}"
database:
entities:
type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql
@ -432,6 +392,10 @@ transport:
auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}"
notifications:
topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
# Local HTTP transport parameters
http:
enabled: "${HTTP_ENABLED:true}"

12
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -72,12 +72,6 @@ public class CoapTransportResource extends CoapResource {
@Override
public void handleGET(CoapExchange exchange) {
if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
exchange.respond(ResponseCode.BAD_REQUEST);
return;
}
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
if (!featureType.isPresent()) {
log.trace("Missing feature type parameter");
@ -108,12 +102,6 @@ public class CoapTransportResource extends CoapResource {
@Override
public void handlePOST(CoapExchange exchange) {
if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
exchange.respond(ResponseCode.BAD_REQUEST);
return;
}
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
if (!featureType.isPresent()) {
log.trace("Missing feature type parameter");

2
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -27,8 +27,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService;
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
import javax.annotation.PostConstruct;

31
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -75,9 +75,6 @@ public class DeviceApiController {
@RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys,
HttpServletRequest httpRequest) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
if (quotaExceeded(httpRequest, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
GetAttributeRequestMsg.Builder request = GetAttributeRequestMsg.newBuilder().setRequestId(0);
@ -100,9 +97,6 @@ public class DeviceApiController {
public DeferredResult<ResponseEntity> postDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest request) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
if (quotaExceeded(request, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
TransportService transportService = transportContext.getTransportService();
@ -116,9 +110,6 @@ public class DeviceApiController {
public DeferredResult<ResponseEntity> postTelemetry(@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest request) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
if (quotaExceeded(request, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
TransportService transportService = transportContext.getTransportService();
@ -133,9 +124,6 @@ public class DeviceApiController {
@RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
HttpServletRequest httpRequest) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
if (quotaExceeded(httpRequest, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
TransportService transportService = transportContext.getTransportService();
@ -153,9 +141,6 @@ public class DeviceApiController {
@PathVariable("requestId") Integer requestId,
@RequestBody String json, HttpServletRequest request) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
if (quotaExceeded(request, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
TransportService transportService = transportContext.getTransportService();
@ -168,9 +153,6 @@ public class DeviceApiController {
public DeferredResult<ResponseEntity> postRpcRequest(@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest httpRequest) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
if (quotaExceeded(httpRequest, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
JsonObject request = new JsonParser().parse(json).getAsJsonObject();
@ -189,9 +171,6 @@ public class DeviceApiController {
@RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
HttpServletRequest httpRequest) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
if (quotaExceeded(httpRequest, responseWriter)) {
return responseWriter;
}
transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
TransportService transportService = transportContext.getTransportService();
@ -204,16 +183,6 @@ public class DeviceApiController {
return responseWriter;
}
private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) {
if (transportContext.getQuotaService().isQuotaExceeded(request.getRemoteAddr())) {
log.warn("REST Quota exceeded for [{}] . Disconnect", request.getRemoteAddr());
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED));
return true;
} else {
return false;
}
}
private static class DeviceAuthCallback implements TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> {
private final TransportContext transportContext;
private final DeferredResult<ResponseEntity> responseWriter;

1
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java

@ -28,7 +28,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService;
import org.thingsboard.server.kafka.TbNodeIdProvider;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;

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

@ -42,7 +42,6 @@ import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -92,7 +91,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private final MqttTransportContext context;
private final MqttTransportAdaptor adaptor;
private final TransportService transportService;
private final QuotaService quotaService;
private final SslHandler sslHandler;
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
@ -106,7 +104,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
this.context = context;
this.transportService = context.getTransportService();
this.adaptor = context.getAdaptor();
this.quotaService = context.getQuotaService();
this.sslHandler = context.getSslHandler();
this.mqttQoSMap = new ConcurrentHashMap<>();
this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap);
@ -129,13 +126,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
processDisconnect(ctx);
return;
}
if (quotaService.isQuotaExceeded(address.getHostName())) {
log.warn("MQTT Quota exceeded for [{}:{}] . Disconnect", address.getHostName(), address.getPort());
processDisconnect(ctx);
return;
}
deviceSessionCtx.setChannel(ctx);
switch (msg.fixedHeader().messageType()) {
case CONNECT:

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

@ -20,7 +20,6 @@ import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService;
import org.thingsboard.server.kafka.TbNodeIdProvider;
import javax.annotation.PostConstruct;
@ -43,9 +42,6 @@ public class TransportContext {
@Autowired
private TbNodeIdProvider nodeIdProvider;
@Autowired(required = false)
private HostRequestsQuotaService quotaService;
@Getter
private ExecutorService executor;

67
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/AbstractQuotaService.java

@ -1,67 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner;
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger;
import org.thingsboard.server.common.transport.quota.inmemory.KeyBasedIntervalRegistry;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class AbstractQuotaService implements QuotaService {
private final KeyBasedIntervalRegistry requestRegistry;
private final RequestLimitPolicy requestsPolicy;
private final IntervalRegistryCleaner registryCleaner;
private final IntervalRegistryLogger registryLogger;
private final boolean enabled;
public AbstractQuotaService(KeyBasedIntervalRegistry requestRegistry, RequestLimitPolicy requestsPolicy,
IntervalRegistryCleaner registryCleaner, IntervalRegistryLogger registryLogger,
boolean enabled) {
this.requestRegistry = requestRegistry;
this.requestsPolicy = requestsPolicy;
this.registryCleaner = registryCleaner;
this.registryLogger = registryLogger;
this.enabled = enabled;
}
@PostConstruct
public void init() {
if (enabled) {
registryCleaner.schedule();
registryLogger.schedule();
}
}
@PreDestroy
public void close() {
if (enabled) {
registryCleaner.stop();
registryLogger.stop();
}
}
@Override
public boolean isQuotaExceeded(String key) {
if (enabled) {
long count = requestRegistry.tick(key);
return !requestsPolicy.isValid(count);
}
return false;
}
}

45
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/Clock.java

@ -1,45 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public final class Clock {
private static long time = 0L;
private Clock() {
}
public static long millis() {
return time == 0 ? System.currentTimeMillis() : time;
}
public static void setMillis(long millis) {
time = millis;
}
public static void shift(long delta) {
time += delta;
}
public static void reset() {
time = 0;
}
}

25
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java

@ -1,25 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public interface QuotaService {
boolean isQuotaExceeded(String key);
}

30
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/RequestLimitPolicy.java

@ -1,30 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
public abstract class RequestLimitPolicy {
private final long limit;
public RequestLimitPolicy(long limit) {
this.limit = limit;
}
public boolean isValid(long currentValue) {
return currentValue <= limit;
}
}

29
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryCleaner.java

@ -1,29 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.host;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner;
@Component
public class HostIntervalRegistryCleaner extends IntervalRegistryCleaner {
public HostIntervalRegistryCleaner(HostRequestIntervalRegistry intervalRegistry,
@Value("${quota.host.cleanPeriodMs}") long cleanPeriodMs) {
super(intervalRegistry, cleanPeriodMs);
}
}

52
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryLogger.java

@ -1,52 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.host;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class HostIntervalRegistryLogger extends IntervalRegistryLogger {
private final long logIntervalMin;
public HostIntervalRegistryLogger(@Value("${quota.host.log.topSize}") int topSize,
@Value("${quota.host.log.intervalMin}") long logIntervalMin,
HostRequestIntervalRegistry intervalRegistry) {
super(topSize, logIntervalMin, intervalRegistry);
this.logIntervalMin = logIntervalMin;
}
protected void log(Map<String, Long> top, int uniqHosts, long requestsCount) {
long rps = requestsCount / TimeUnit.MINUTES.toSeconds(logIntervalMin);
StringBuilder builder = new StringBuilder("Quota Statistic : ");
builder.append("uniqHosts : ").append(uniqHosts).append("; ");
builder.append("requestsCount : ").append(requestsCount).append("; ");
builder.append("RPS : ").append(rps).append(" ");
builder.append("top -> ");
for (Map.Entry<String, Long> host : top.entrySet()) {
builder.append(host.getKey()).append(" : ").append(host.getValue()).append("; ");
}
log.info(builder.toString());
}
}

37
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestIntervalRegistry.java

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.host;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.inmemory.KeyBasedIntervalRegistry;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
@Component
@Slf4j
public class HostRequestIntervalRegistry extends KeyBasedIntervalRegistry {
public HostRequestIntervalRegistry(@Value("${quota.host.intervalMs}") long intervalDurationMs,
@Value("${quota.host.ttlMs}") long ttlMs,
@Value("${quota.host.whitelist}") String whiteList,
@Value("${quota.host.blacklist}") String blackList) {
super(intervalDurationMs, ttlMs, whiteList, blackList, "host");
}
}

33
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestLimitPolicy.java

@ -1,33 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.host;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.RequestLimitPolicy;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
@Component
public class HostRequestLimitPolicy extends RequestLimitPolicy {
public HostRequestLimitPolicy(@Value("${quota.host.limit}") long limit) {
super(limit);
}
}

37
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestsQuotaService.java

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.host;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.quota.AbstractQuotaService;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
@Service
@Slf4j
public class HostRequestsQuotaService extends AbstractQuotaService {
public HostRequestsQuotaService(HostRequestIntervalRegistry requestRegistry, HostRequestLimitPolicy requestsPolicy,
HostIntervalRegistryCleaner registryCleaner, HostIntervalRegistryLogger registryLogger,
@Value("${quota.host.enabled}") boolean enabled) {
super(requestRegistry, requestsPolicy, registryCleaner, registryLogger, enabled);
}
}

68
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCount.java

@ -1,68 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import org.thingsboard.server.common.transport.quota.Clock;
import java.util.concurrent.atomic.LongAdder;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class IntervalCount {
private final LongAdder adder = new LongAdder();
private final long intervalDurationMs;
private volatile long startTime;
private volatile long lastTickTime;
public IntervalCount(long intervalDurationMs) {
this.intervalDurationMs = intervalDurationMs;
startTime = Clock.millis();
}
public long resetIfExpiredAndTick() {
if (isExpired()) {
reset();
}
tick();
return adder.sum();
}
public long silenceDuration() {
return Clock.millis() - lastTickTime;
}
public long getCount() {
return adder.sum();
}
private void tick() {
adder.add(1);
lastTickTime = Clock.millis();
}
private void reset() {
adder.reset();
startTime = Clock.millis();
}
private boolean isExpired() {
return (Clock.millis() - startTime) > intervalDurationMs;
}
}

62
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryCleaner.java

@ -1,62 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
@Slf4j
public abstract class IntervalRegistryCleaner {
private final KeyBasedIntervalRegistry intervalRegistry;
private final long cleanPeriodMs;
private ScheduledExecutorService executor;
public IntervalRegistryCleaner(KeyBasedIntervalRegistry intervalRegistry, long cleanPeriodMs) {
this.intervalRegistry = intervalRegistry;
this.cleanPeriodMs = cleanPeriodMs;
}
public void schedule() {
if (executor != null) {
throw new IllegalStateException("Registry Cleaner already scheduled");
}
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::clean, cleanPeriodMs, cleanPeriodMs, TimeUnit.MILLISECONDS);
}
public void stop() {
if (executor != null) {
executor.shutdown();
}
}
public void clean() {
try {
intervalRegistry.clean();
} catch (RuntimeException ex) {
log.error("Could not clear Interval Registry", ex);
}
}
}

79
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLogger.java

@ -1,79 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import com.google.common.collect.MinMaxPriorityQueue;
import lombok.extern.slf4j.Slf4j;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
@Slf4j
public abstract class IntervalRegistryLogger {
private final int topSize;
private final KeyBasedIntervalRegistry intervalRegistry;
private final long logIntervalMin;
private ScheduledExecutorService executor;
public IntervalRegistryLogger(int topSize, long logIntervalMin, KeyBasedIntervalRegistry intervalRegistry) {
this.topSize = topSize;
this.logIntervalMin = logIntervalMin;
this.intervalRegistry = intervalRegistry;
}
public void schedule() {
if (executor != null) {
throw new IllegalStateException("Registry Cleaner already scheduled");
}
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::logStatistic, logIntervalMin, logIntervalMin, TimeUnit.MINUTES);
}
public void stop() {
if (executor != null) {
executor.shutdown();
}
}
public void logStatistic() {
Map<String, Long> registryContent = intervalRegistry.getContent();
int uniqHosts = registryContent.size();
long requestsCount = registryContent.values().stream().mapToLong(i -> i).sum();
Map<String, Long> top = getTopElements(registryContent);
log(top, uniqHosts, requestsCount);
}
protected Map<String, Long> getTopElements(Map<String, Long> countMap) {
MinMaxPriorityQueue<Map.Entry<String, Long>> topQueue = MinMaxPriorityQueue
.orderedBy(Comparator.comparing((Function<Map.Entry<String, Long>, Long>) Map.Entry::getValue).reversed())
.maximumSize(topSize)
.create(countMap.entrySet());
return topQueue.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
protected abstract void log(Map<String, Long> top, int uniqHosts, long requestsCount);
}

73
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/KeyBasedIntervalRegistry.java

@ -1,73 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
public abstract class KeyBasedIntervalRegistry {
private final Map<String, IntervalCount> hostCounts = new ConcurrentHashMap<>();
private final long intervalDurationMs;
private final long ttlMs;
private final Set<String> whiteList;
private final Set<String> blackList;
public KeyBasedIntervalRegistry(long intervalDurationMs, long ttlMs, String whiteList, String blackList, String name) {
this.intervalDurationMs = intervalDurationMs;
this.ttlMs = ttlMs;
this.whiteList = Sets.newHashSet(StringUtils.split(whiteList, ','));
this.blackList = Sets.newHashSet(StringUtils.split(blackList, ','));
validate(name);
}
private void validate(String name) {
if (ttlMs < intervalDurationMs) {
log.warn("TTL for {} IntervalRegistry [{}] smaller than interval duration [{}]", name, ttlMs, intervalDurationMs);
}
log.info("Start {} KeyBasedIntervalRegistry with whitelist {}", name, whiteList);
log.info("Start {} KeyBasedIntervalRegistry with blacklist {}", name, blackList);
}
public long tick(String clientHostId) {
IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs));
long currentCount = intervalCount.resetIfExpiredAndTick();
if (whiteList.contains(clientHostId)) {
return 0;
} else if (blackList.contains(clientHostId)) {
return Long.MAX_VALUE;
}
return currentCount;
}
public void clean() {
hostCounts.entrySet().removeIf(entry -> entry.getValue().silenceDuration() > ttlMs);
}
public Map<String, Long> getContent() {
return hostCounts.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
interval -> interval.getValue().getCount()));
}
}

31
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java

@ -1,31 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.tenant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner;
@Component
@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantIntervalRegistryCleaner extends IntervalRegistryCleaner {
public TenantIntervalRegistryCleaner(TenantMsgsIntervalRegistry intervalRegistry,
@Value("${quota.rule.tenant.cleanPeriodMs}") long cleanPeriodMs) {
super(intervalRegistry, cleanPeriodMs);
}
}

54
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java

@ -1,54 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.tenant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantIntervalRegistryLogger extends IntervalRegistryLogger {
private final long logIntervalMin;
public TenantIntervalRegistryLogger(@Value("${quota.rule.tenant.log.topSize}") int topSize,
@Value("${quota.rule.tenant.log.intervalMin}") long logIntervalMin,
TenantMsgsIntervalRegistry intervalRegistry) {
super(topSize, logIntervalMin, intervalRegistry);
this.logIntervalMin = logIntervalMin;
}
protected void log(Map<String, Long> top, int uniqHosts, long requestsCount) {
long rps = requestsCount / TimeUnit.MINUTES.toSeconds(logIntervalMin);
StringBuilder builder = new StringBuilder("Tenant Quota Statistic : ");
builder.append("uniqTenants : ").append(uniqHosts).append("; ");
builder.append("requestsCount : ").append(requestsCount).append("; ");
builder.append("RPS : ").append(rps).append(" ");
builder.append("top -> ");
for (Map.Entry<String, Long> host : top.entrySet()) {
builder.append(host.getKey()).append(" : ").append(host.getValue()).append("; ");
}
log.info(builder.toString());
}
}

33
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java

@ -1,33 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.tenant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.inmemory.KeyBasedIntervalRegistry;
@Component
@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantMsgsIntervalRegistry extends KeyBasedIntervalRegistry {
public TenantMsgsIntervalRegistry(@Value("${quota.rule.tenant.intervalMs}") long intervalDurationMs,
@Value("${quota.rule.tenant.ttlMs}") long ttlMs,
@Value("${quota.rule.tenant.whitelist}") String whiteList,
@Value("${quota.rule.tenant.blacklist}") String blackList) {
super(intervalDurationMs, ttlMs, whiteList, blackList, "Rule Tenant");
}
}

32
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java

@ -1,32 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.tenant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.AbstractQuotaService;
@Component
@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantQuotaService extends AbstractQuotaService {
public TenantQuotaService(TenantMsgsIntervalRegistry requestRegistry, TenantRequestLimitPolicy requestsPolicy,
TenantIntervalRegistryCleaner registryCleaner, TenantIntervalRegistryLogger registryLogger,
@Value("${quota.rule.tenant.enabled}") boolean enabled) {
super(requestRegistry, requestsPolicy, registryCleaner, registryLogger, enabled);
}
}

30
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java

@ -1,30 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.tenant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.quota.RequestLimitPolicy;
@Component
@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantRequestLimitPolicy extends RequestLimitPolicy {
public TenantRequestLimitPolicy(@Value("${quota.rule.tenant.limit}") long limit) {
super(limit);
}
}

66
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java

@ -1,66 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class ClockTest {
@Before
public void init() {
Clock.reset();
}
@After
public void clear() {
Clock.reset();
}
@Test
public void defaultClockUseSystemTime() {
assertFalse(Clock.millis() > System.currentTimeMillis());
}
@Test
public void timeCanBeSet() {
Clock.setMillis(100L);
assertEquals(100L, Clock.millis());
}
@Test
public void clockCanBeReseted() {
Clock.setMillis(100L);
assertEquals(100L, Clock.millis());
Clock.reset();
assertFalse(Clock.millis() > System.currentTimeMillis());
}
@Test
public void timeIsShifted() {
Clock.setMillis(100L);
Clock.shift(50L);
assertEquals(150L, Clock.millis());
}
}

47
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestLimitPolicyTest.java

@ -1,47 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
import org.junit.Test;
import org.thingsboard.server.common.transport.quota.host.HostRequestLimitPolicy;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class HostRequestLimitPolicyTest {
private HostRequestLimitPolicy limitPolicy = new HostRequestLimitPolicy(10L);
@Test
public void ifCurrentValueLessThenLimitItIsValid() {
assertTrue(limitPolicy.isValid(9));
}
@Test
public void ifCurrentValueEqualsToLimitItIsValid() {
assertTrue(limitPolicy.isValid(10));
}
@Test
public void ifCurrentValueGreaterThenLimitItIsValid() {
assertFalse(limitPolicy.isValid(11));
}
}

78
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java

@ -1,78 +0,0 @@
/**
* Copyright © 2016-2018 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.quota;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.transport.quota.host.HostIntervalRegistryCleaner;
import org.thingsboard.server.common.transport.quota.host.HostIntervalRegistryLogger;
import org.thingsboard.server.common.transport.quota.host.HostRequestIntervalRegistry;
import org.thingsboard.server.common.transport.quota.host.HostRequestLimitPolicy;
import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class HostRequestsQuotaServiceTest {
private HostRequestsQuotaService quotaService;
private HostRequestIntervalRegistry requestRegistry = mock(HostRequestIntervalRegistry.class);
private HostRequestLimitPolicy requestsPolicy = mock(HostRequestLimitPolicy.class);
private HostIntervalRegistryCleaner registryCleaner = mock(HostIntervalRegistryCleaner.class);
private HostIntervalRegistryLogger registryLogger = mock(HostIntervalRegistryLogger.class);
@Before
public void init() {
quotaService = new HostRequestsQuotaService(requestRegistry, requestsPolicy, registryCleaner, registryLogger, true);
}
@Test
public void quotaExceededIfRequestCountBiggerThanAllowed() {
when(requestRegistry.tick("key")).thenReturn(10L);
when(requestsPolicy.isValid(10L)).thenReturn(false);
assertTrue(quotaService.isQuotaExceeded("key"));
verify(requestRegistry).tick("key");
verify(requestsPolicy).isValid(10L);
verifyNoMoreInteractions(requestRegistry, requestsPolicy);
}
@Test
public void quotaNotExceededIfRequestCountLessThanAllowed() {
when(requestRegistry.tick("key")).thenReturn(10L);
when(requestsPolicy.isValid(10L)).thenReturn(true);
assertFalse(quotaService.isQuotaExceeded("key"));
verify(requestRegistry).tick("key");
verify(requestsPolicy).isValid(10L);
verifyNoMoreInteractions(requestRegistry, requestsPolicy);
}
@Test
public void serviceCanBeDisabled() {
quotaService = new HostRequestsQuotaService(requestRegistry, requestsPolicy, registryCleaner, registryLogger, false);
assertFalse(quotaService.isQuotaExceeded("key"));
verifyNoMoreInteractions(requestRegistry, requestsPolicy);
}
}

83
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistryTest.java

@ -1,83 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.transport.quota.host.HostRequestIntervalRegistry;
import static org.junit.Assert.assertEquals;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class HostRequestIntervalRegistryTest {
private HostRequestIntervalRegistry registry;
@Before
public void init() {
registry = new HostRequestIntervalRegistry(10000L, 100L,"g1,g2", "b1");
}
@Test
public void newHostCreateNewInterval() {
assertEquals(1L, registry.tick("host1"));
}
@Test
public void existingHostUpdated() {
registry.tick("aaa");
assertEquals(1L, registry.tick("bbb"));
assertEquals(2L, registry.tick("aaa"));
}
@Test
public void expiredIntervalsCleaned() throws InterruptedException {
registry.tick("aaa");
Thread.sleep(150L);
registry.tick("bbb");
registry.clean();
assertEquals(1L, registry.tick("aaa"));
assertEquals(2L, registry.tick("bbb"));
}
@Test
public void domainFromWhitelistNotCounted(){
assertEquals(0L, registry.tick("g1"));
assertEquals(0L, registry.tick("g1"));
assertEquals(0L, registry.tick("g2"));
}
@Test
public void domainFromBlackListReturnMaxValue(){
assertEquals(Long.MAX_VALUE, registry.tick("b1"));
assertEquals(Long.MAX_VALUE, registry.tick("b1"));
}
@Test
public void emptyWhitelistParsedOk(){
registry = new HostRequestIntervalRegistry(10000L, 100L,"", "b1");
assertEquals(1L, registry.tick("aaa"));
}
@Test
public void emptyBlacklistParsedOk(){
registry = new HostRequestIntervalRegistry(10000L, 100L,"", "");
assertEquals(1L, registry.tick("aaa"));
}
}

65
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCountTest.java

@ -1,65 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.transport.quota.Clock;
import static org.junit.Assert.assertEquals;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class IntervalCountTest {
@Before
public void init() {
Clock.setMillis(1000L);
}
@After
public void clear() {
Clock.reset();
}
@Test
public void ticksInSameIntervalAreSummed() {
IntervalCount intervalCount = new IntervalCount(100L);
assertEquals(1L, intervalCount.resetIfExpiredAndTick());
Clock.shift(100);
assertEquals(2L, intervalCount.resetIfExpiredAndTick());
}
@Test
public void oldDataCleanedWhenIntervalExpired() {
IntervalCount intervalCount = new IntervalCount(100L);
assertEquals(1L, intervalCount.resetIfExpiredAndTick());
Clock.shift(101);
assertEquals(1L, intervalCount.resetIfExpiredAndTick());
}
@Test
public void silenceDurationCalculatedFromLastTick() {
IntervalCount intervalCount = new IntervalCount(100L);
assertEquals(1L, intervalCount.resetIfExpiredAndTick());
Clock.shift(10L);
assertEquals(10L, intervalCount.silenceDuration());
}
}

63
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLoggerTest.java

@ -1,63 +0,0 @@
/**
* Copyright © 2016-2018 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.quota.inmemory;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.transport.quota.host.HostIntervalRegistryLogger;
import org.thingsboard.server.common.transport.quota.host.HostRequestIntervalRegistry;
import java.util.Collections;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
/**
* @author Vitaliy Paromskiy
* @version 1.0
*/
public class IntervalRegistryLoggerTest {
private IntervalRegistryLogger logger;
private HostRequestIntervalRegistry requestRegistry = mock(HostRequestIntervalRegistry.class);
@Before
public void init() {
logger = new HostIntervalRegistryLogger(3, 10, requestRegistry);
}
@Test
public void onlyMaxHostsCollected() {
Map<String, Long> map = ImmutableMap.of("a", 8L, "b", 3L, "c", 1L, "d", 3L);
Map<String, Long> actual = logger.getTopElements(map);
Map<String, Long> expected = ImmutableMap.of("a", 8L, "b", 3L, "d", 3L);
assertEquals(expected, actual);
}
@Test
public void emptyMapProcessedCorrectly() {
Map<String, Long> map = Collections.emptyMap();
Map<String, Long> actual = logger.getTopElements(map);
Map<String, Long> expected = Collections.emptyMap();
assertEquals(expected, actual);
}
}

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java

@ -62,15 +62,15 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService {
@Override
@CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, keyGenerator="previousDeviceCredentialsId", beforeInvocation = true)
public DeviceCredentials updateDeviceCredentials(DeviceCredentials deviceCredentials) {
return saveOrUpdare(deviceCredentials);
return saveOrUpdate(deviceCredentials);
}
@Override
public DeviceCredentials createDeviceCredentials(DeviceCredentials deviceCredentials) {
return saveOrUpdare(deviceCredentials);
return saveOrUpdate(deviceCredentials);
}
private DeviceCredentials saveOrUpdare(DeviceCredentials deviceCredentials) {
private DeviceCredentials saveOrUpdate(DeviceCredentials deviceCredentials) {
if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) {
formatCertData(deviceCredentials);
}

2
msa/docker/.env

@ -10,4 +10,4 @@ COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport
TB_VERSION=2.2.0-SNAPSHOT
KAFKA_TOPICS=js.eval.requests:100:1:delete --config=retention.ms=60000 --config=retention.bytes=1073741824,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=retention.bytes=1073741824,tb.rule-engine:30:1
KAFKA_TOPICS=js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1

1
msa/docker/docker-compose.yml

@ -37,6 +37,7 @@ services:
KAFKA_CREATE_TOPICS: "${KAFKA_TOPICS}"
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
KAFKA_LOG_RETENTION_BYTES: 1073741824
KAFKA_LOG_SEGMENT_BYTES: 268435456
KAFKA_LOG_RETENTION_MS: 300000
KAFKA_LOG_CLEANUP_POLICY: delete
depends_on:

2
pom.xml

@ -66,7 +66,7 @@
<paho.client.version>1.1.0</paho.client.version>
<netty.version>4.1.22.Final</netty.version>
<os-maven-plugin.version>1.5.0</os-maven-plugin.version>
<rabbitmq.version>3.6.5</rabbitmq.version>
<rabbitmq.version>4.8.0</rabbitmq.version>
<surfire.version>2.19.1</surfire.version>
<jar-plugin.version>3.0.2</jar-plugin.version>
<springfox-swagger.version>2.6.1</springfox-swagger.version>

25
transport/coap/src/main/resources/tb-coap-transport.yml

@ -23,27 +23,10 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}"
#Quota parameters
quota:
host:
# Max allowed number of API requests in interval for single host
limit: "${QUOTA_HOST_LIMIT:10000}"
# Interval duration
intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
# Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
# Interval for scheduled task that cleans expired records. TTL is used for expiring
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
# Enable Host API Limits
enabled: "${QUOTA_HOST_ENABLED:true}"
# Array of whitelist hosts
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
# Array of blacklist hosts
blacklist: "${QUOTA_HOST_BLACKLIST:}"
log:
topSize: 10
intervalMin: 2
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
kafka:
enabled: true

25
transport/http/src/main/resources/tb-http-transport.yml

@ -24,27 +24,10 @@ server:
transport:
http:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
#Quota parameters
quota:
host:
# Max allowed number of API requests in interval for single host
limit: "${QUOTA_HOST_LIMIT:10000}"
# Interval duration
intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
# Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
# Interval for scheduled task that cleans expired records. TTL is used for expiring
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
# Enable Host API Limits
enabled: "${QUOTA_HOST_ENABLED:true}"
# Array of whitelist hosts
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
# Array of blacklist hosts
blacklist: "${QUOTA_HOST_BLACKLIST:}"
log:
topSize: 10
intervalMin: 2
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
kafka:
enabled: true

21
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -51,27 +51,6 @@ transport:
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
#Quota parameters
quota:
host:
# Max allowed number of API requests in interval for single host
limit: "${QUOTA_HOST_LIMIT:10000}"
# Interval duration
intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
# Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
# Interval for scheduled task that cleans expired records. TTL is used for expiring
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
# Enable Host API Limits
enabled: "${QUOTA_HOST_ENABLED:true}"
# Array of whitelist hosts
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
# Array of blacklist hosts
blacklist: "${QUOTA_HOST_BLACKLIST:}"
log:
topSize: 10
intervalMin: 2
kafka:
enabled: true
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"

18
ui/src/app/api/datasource.service.js

@ -104,6 +104,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
var listeners = [];
var datasourceType = datasourceSubscription.datasourceType;
var datasourceData = {};
var dataSourceOrigData = {};
var dataKeys = {};
var subscribers = [];
var history = datasourceSubscription.subscriptionTimewindow &&
@ -140,7 +141,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
}
} else {
if (dataKey.postFuncBody && !dataKey.postFunc) {
dataKey.postFunc = new Function("time", "value", "prevValue", dataKey.postFuncBody);
dataKey.postFunc = new Function("time", "value", "prevValue", "timePrev", "prevOrigValue", dataKey.postFuncBody);
}
}
if (datasourceType === types.datasourceType.entity || datasourceSubscription.type === types.widgetType.timeseries.value) {
@ -165,6 +166,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
};
dataKeys[key] = dataKey;
}
dataSourceOrigData = angular.copy(datasourceData);
dataKey.key = key;
}
if (datasourceType === types.datasourceType.function) {
@ -678,27 +680,36 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
var dataKey = dataKeyList[keyIndex];
var data = [];
var prevSeries;
var prevOrigSeries;
var datasourceKeyData;
var datasourceOrigKeyData;
var update = false;
if (realtime) {
datasourceKeyData = [];
datasourceOrigKeyData = [];
} else {
datasourceKeyData = datasourceData[datasourceKey].data;
datasourceOrigKeyData = dataSourceOrigData[datasourceKey].data;
}
if (datasourceKeyData.length > 0) {
prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length -1];
} else {
prevSeries = [0, 0];
prevOrigSeries = [0, 0];
}
dataSourceOrigData[datasourceKey].data = [];
if (datasourceSubscription.type === types.widgetType.timeseries.value) {
var series, time, value;
for (var i = 0; i < keyData.length; i++) {
series = keyData[i];
time = series[0];
dataSourceOrigData[datasourceKey].data.push(series);
value = convertValue(series[1]);
if (dataKey.postFunc) {
value = dataKey.postFunc(time, value, prevSeries[1]);
value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]);
}
prevOrigSeries = series;
series = [time, value];
data.push(series);
prevSeries = series;
@ -708,9 +719,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
if (keyData.length > 0) {
series = keyData[0];
time = series[0];
dataSourceOrigData[datasourceKey].data.push(series);
value = convertValue(series[1]);
if (dataKey.postFunc) {
value = dataKey.postFunc(time, value, prevSeries[1]);
value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]);
}
series = [time, value];
data.push(series);

11
ui/src/app/components/datakey-config.tpl.html

@ -75,9 +75,16 @@
</md-checkbox>
<tb-js-func ng-if="model.usePostProcessing"
ng-model="model.postFuncBody"
function-args="{{ ['time', 'value', 'prevValue'] }}"
validation-args="{{ [[1, 1, 1],[1, '1', '1']] }}"
function-args="{{ ['time', 'value', 'prevValue', 'timePrev', 'prevOrigValue'] }}"
validation-args="{{ [[1, 1, 1, 1, 1],[1, '1', '1', 1, '1']] }}"
result-type="any">
</tb-js-func>
<label ng-if="model.usePostProcessing" class="tb-title" style="margin-left: 15px;">
time - {{ 'datakey.time-description' | translate }}</br>
value - {{ 'datakey.value-description' | translate }}</br>
prevValue - {{ 'datakey.prev-value-description' | translate }}</br>
timePrev - {{ 'datakey.time-prev-description' | translate }}</br>
prevOrigValue - {{ 'datakey.prev-orig-value-description' | translate }}
</label>
</section>
</md-content>

2344
ui/src/app/locale/locale.constant-TR.json

File diff suppressed because it is too large

7
ui/src/app/locale/locale.constant-en_US.json

@ -555,7 +555,12 @@
"alarm-fields-required": "Alarm fields are required.",
"function-types": "Function types",
"function-types-required": "Function types are required.",
"maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }"
"maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }",
"time-description": "timestamp of the current value;",
"value-description": "the current value;",
"prev-value-description": "result of the previous function call;",
"time-prev-description": "timestamp of the previous value;",
"prev-orig-value-description": "original previous value;"
},
"datasource": {
"type": "Datasource type",

Loading…
Cancel
Save